Tłumaczenie automatyczne · Oryginał angielski
API i protokoły
Native Apple runtime · PockettTS
PocketTTS dodaje rodzimą syntezę mowy CoreML do speech-swift. Wszystkie etapy domyślne do wykonania tylko CPU. angielskie strumienie Alba preset 1,920- próbki w 24 kHz (80 ms per frame).
import PocketTTS
let model = try PocketTTSModel.fromBundle(URL(fileURLWithPath: "/path/to/bundle"))
try await model.generateStreaming(text: "Hello world.") { chunk in
await consume(chunk.samples)
}W AudioCommon moduł definiuje protokoły model- agnostyczne i typy współdzielone. Każdy model zgodny może być stosowany zamiennie przez te interfejsy.
Przegląd protokołu
┌─────────────────────────────────────────────────────────┐
│ AudioCommon │
│ │
│ AudioChunk SpeechGenerationModel (TTS) │
│ AlignedWord SpeechRecognitionModel (STT) │
│ SpeechSegment ForcedAlignmentModel │
│ SpeechToSpeechModel │
│ VoiceActivityDetectionModel (VAD) │
│ TurnCompletionProvider (EOT) │
│ SpeakerEmbeddingModel │
│ SpeakerDiarizationModel │
│ SpeakerExtractionCapable │
└─────────────────────────────────────────────────────────┘Mowa o uznaniu modelu
Protokół dotyczący modeli przemówień i tekstów.
public protocol SpeechRecognitionModel: AnyObject {
var inputSampleRate: Int { get }
func transcribe(audio: [Float], sampleRate: Int, language: String?) -> String
func transcribeWithLanguage(audio: [Float], sampleRate: Int, language: String?) -> TranscriptionResult
}
Konfigurowanie typów: Qwen3ASRModel, WhisperASRModel, ParakeetASRModel, ParakeetStreamingASRModel, OmnilingualASRModel (CoreML), OmnilingualASRMLXModel (MLX)
Mowa GenerationModel
Protokół dla modeli tekstowych do mowy.
public protocol SpeechGenerationModel: AnyObject {
var sampleRate: Int { get }
func generate(text: String, language: String?) async throws -> [Float]
func generateStream(text: String, language: String?) -> AsyncThrowingStream<AudioChunk, Error> // has default impl
}
generateStream() ma domyślną implementację, która zawija generate() jako pojedynczy kawałek. Modele z prawdziwym strumieniowaniem (np. Qwen3-TTS) go nadrabiają.
Konfigurowanie typów: Qwen3TTSModel, CosyVoiceTTSModel, VoxCPM2TTSModel, KokoroTTSModel, IndexTTS2TTSModel
IndexTTS2TTSModel dodaje reference- audio generate przeciążenie do klonowania głosu zeroshot i zastosowań IndexTTS2SynthesisOptions dla głośnomówiących i pauzy kontroli.
Lokalne ładowanie Qwen3-TTS
fromLocal walidates wymagane pliki i kompletność odłamka kontrolnego, a następnie wywodzi rozmiar modelu, kwantyfikacji i architektury z config.json. Opcjonalnie configuration wartość jest tylko zwrotem dla pominiętych metadanych; błędne lub sprzeczne metadane rzuca Qwen3TTSLoadingError.invalidConfiguration przed przydzieleniem modelu.
public static func fromLocal(
modelDirectory: URL,
tokenizerDirectory: URL,
configuration: Qwen3TTSConfig? = nil,
wiredMemoryPolicy: Qwen3TTSWiredMemoryPolicy = .none,
progressHandler: ((Double, String) -> Void)? = nil
) throws -> Qwen3TTSModel
public static func fromPretrained(
modelId: String = Qwen3TTSModel.defaultModelId,
tokenizerModelId: String = "Qwen/Qwen3-TTS-Tokenizer-12Hz",
cacheDir: URL? = nil,
tokenizerCacheDir: URL? = nil,
offlineMode: Bool = false,
wiredMemoryPolicy: Qwen3TTSWiredMemoryPolicy = .pin(fraction: 0.9),
progressHandler: ((Double, String) -> Void)? = nil
) async throws -> Qwen3TTSModel
fromLocal domyślne .none, więc nie zmienia limitu pamięci przewodowej Metal. fromPretrained zachowuje swoje .pin(fraction: 0.9) Domyślny. Oba wstępnie przeszkoleni ładowarki akceptują oddzielny tokenizerCacheDir.
ForcedAlignmentModel
Protokół dotyczący ustawiania znaczników czasu na poziomie słownym.
public protocol ForcedAlignmentModel: AnyObject {
func align(audio: [Float], text: String, sampleRate: Int, language: String?) -> [AlignedWord]
}
Mowa ToSpeechModel
Protokół w sprawie modeli dialogu mowy.
public protocol SpeechToSpeechModel: AnyObject {
var sampleRate: Int { get }
func respond(userAudio: [Float]) -> [Float]
func respondStream(userAudio: [Float]) -> AsyncThrowingStream<AudioChunk, Error>
}
Konfigurowanie typów: PersonaPlexModel
HibikiTranslateModel
Sterownik tłumaczenia mowy-to@-@ mowy. Nie odpowiada SpeechToSpeechModel ponieważ umowa jest inna: wejście i wyjście są różnymi językami, długość wyjścia jest zmienna (EOS- driven, capped at max(tSrc * 5/2, tSrc + 20) kroki - mniej więcej 1.0- 1.6 × czas trwania źródła na klipach w stylu FLEURS-), a obok dźwięku znajduje się równoległy strumień text- token. Pełny przewodnik →
public final class HibikiTranslateModel {
public let cfg: HibikiConfig
public static var defaultModelId: String { "aufklarer/Hibiki-Zero-3B-MLX-4bit" }
public static var modelId8bit: String { "aufklarer/Hibiki-Zero-3B-MLX-8bit" }
public static func fromPretrained(
modelId: String = defaultModelId,
cacheDir: URL? = nil,
offlineMode: Bool = false,
progressHandler: ((Double, String) -> Void)? = nil
) async throws -> HibikiTranslateModel
public func warmUp()
public func translate(
sourceAudio: [Float],
sourceLanguage: HibikiSourceLanguage = .fr,
verbose: Bool = false
) -> (audio: [Float], textTokens: [Int32])
public func translateStream(
sourceAudio: [Float],
sourceLanguage: HibikiSourceLanguage = .fr,
streaming: HibikiStreamingConfig = .default,
verbose: Bool = false
) -> AsyncThrowingStream<AudioChunk, Error>
}
public enum HibikiSourceLanguage: String, CaseIterable, Sendable, Codable {
case fr, es, pt, de
}
Szybkość próbkowania: 24 kHz mono, zarówno wejście jak i wyjście. Wskaźnik ramki: 12.5 Hz (kodek Mimi). Języki źródłowe: FR / ES / PT / DE. Język docelowy: PL. Ustawienie HIBIKI_GREEDY=1 przy uruchomieniu procesu deterministycznego dekodowania argmax (pasuje do kanarków CI).
VoiceActivityDetectionModel
Protokół dotyczący wykrywania aktywności głosowej.
public protocol VoiceActivityDetectionModel: AnyObject {
var inputSampleRate: Int { get }
func detectSpeech(audio: [Float], sampleRate: Int) -> [SpeechSegment]
}
TurnCompletionProvider
Protokół dla klasyfikatorów typu end-of@-@ turn, z którym skonsultowano się StreamingVADProcessor w każdym potwierdzonym VAD A VAD tylko słyszy ciszę; model return-completion słucha prosodii całej wypowiedzi, więc pauza w połowie zdania trzyma agenta czekającego, podczas gdy kończy zdanie otrzymuje natychmiastową odpowiedź. speech-corejest sc_turn_completion_vtable_tPatrz Przewodnik Smart Turn.
public protocol TurnCompletionProvider: AnyObject {
/// Probability in [0, 1] that the turn is complete, given the audio of the
/// turn so far (Smart Turn looks at the last 8 s).
func turnCompleteProbability(audio: [Float], sampleRate: Int) throws -> Float
}
Konfigurowanie typów: SmartTurnModel (Smart Turn v3.2)
Speaker EmbeddingModel
Protokół dotyczący ekstrakcji głośników.
public protocol SpeakerEmbeddingModel: AnyObject {
var inputSampleRate: Int { get }
var embeddingDimension: Int { get }
func embed(audio: [Float], sampleRate: Int) -> [Float]
}
Konfigurowanie typów: WeSpeakerModel
SpeakerDiarizationModel
Protokół dla modeli diaryzacji głośników, które przypisują etykiety głośników segmentom audio.
public protocol SpeakerDiarizationModel: AnyObject {
var inputSampleRate: Int { get }
func diarize(audio: [Float], sampleRate: Int) -> [DiarizedSegment]
}
Konfigurowanie typów: DiarizationPipeline (Pyannote), SortformerDiarizer
SpeakerExtractionCapable
Rozszerzony protokół diaryzacji dla silników, które obsługują ekstrakcję segmentów głośnika docelowego za pomocą wzorcowego osadzania. Nie wszystkie silniki wspierają to (Sortformer jest end-to@-@ end i nie produkuje osadzania głośników).
public protocol SpeakerExtractionCapable: SpeakerDiarizationModel {
func extractSpeaker(audio: [Float], sampleRate: Int, targetEmbedding: [Float]) -> [SpeechSegment]
}
Konfigurowanie typów: DiarizationPipeline (Tylko Pyannote)
Współdzielone typy
AudioChunk
public struct AudioChunk {
public let samples: [Float] // PCM samples
public let sampleRate: Int // Sample rate (e.g. 24000)
}
Przemówienie
public struct SpeechSegment {
public let startTime: Float // Start time in seconds
public let endTime: Float // End time in seconds
}
AlignedWordName
public struct AlignedWord {
public let text: String // The word
public let startTime: Float // Start time in seconds
public let endTime: Float // End time in seconds
}
DiarizedSegment
public struct DiarizedSegment {
public let startTime: Float // Start time in seconds
public let endTime: Float // End time in seconds
public let speakerId: Int // Speaker identifier (0-based)
}
DialogueSegment
Parsowany segment tekstu dialogu wielogłośnikowego z opcjonalnymi znacznikami głośnika i emocji. DialogueParser oraz DialogueSynthesizer dla syntezy dialogu CosyVoice3.
public struct DialogueSegment: Sendable, Equatable {
public let speaker: String? // Speaker identifier ("S1", "S2"), nil for untagged
public let emotion: String? // Emotion tag ("happy", "whispers"), nil if none
public let text: String // Cleaned text to synthesize
}
DialogueParser
Tekst wielogłośnikowego dialogu z tagami głośników ([S1]) i znaczniki emocji ((happy)).
public enum DialogueParser {
static func parse(_ text: String) -> [DialogueSegment]
static func emotionToInstruction(_ emotion: String) -> String
}
Built- in emotions: happy/excited, sad, angry, whispers/whispering, laughs/laughing, calm, surprised, serious. Nieznane znaczniki przechodzą jako instrukcje freeform.
Dialogue Synthesizer
Organizuje wielosegmentową syntezę dialogu z klonowaniem głosu przemówcy, lukami milczącymi i przemijającym.
public enum DialogueSynthesizer {
static func synthesize(
segments: [DialogueSegment],
speakerEmbeddings: [String: [Float]],
model: CosyVoiceTTSModel,
language: String,
config: DialogueSynthesisConfig,
verbose: Bool
) -> [Float]
}
Dialogue SynthesisConfig
public struct DialogueSynthesisConfig: Sendable {
public var turnGapSeconds: Float // Default: 0.2
public var crossfadeSeconds: Float // Default: 0.0
public var defaultInstruction: String // Default: "You are a helpful assistant."
public var maxTokensPerSegment: Int // Default: 500
}
PipelineLLM
Protokół integracji modelu językowego z rurociągami głosowymi. LLM do VoicePipeline ASR → LLM → TTS Przepływ.
public protocol PipelineLLM: AnyObject {
func chat(messages: [(role: MessageRole, content: String)],
onToken: @escaping (String, Bool) -> Void)
func cancel()
}
Adapter wbudowany: Qwen3PipelineLLM mosty Qwen35MLXChat do tego protokołu z tokenami czyszczenie, anulowanie, i oczekujących na nagromadzenie wyrażenia.
AudioIO
Wielofunkcyjny menedżer audio I / O, który eliminuje płytkę magnetyczną AVAudioEngine. Obsługuje mikrofony, przełączanie, odtwarzanie i pomiar poziomu dźwięku.
let audio = AudioIO()
try audio.startMicrophone(targetSampleRate: 16000) { samples in
pipeline.pushAudio(samples)
}
audio.player.scheduleChunk(ttsOutput)
audio.stopMicrophone()
AudioIO zawiera StreamingAudioPlayer dla wyjścia TTS i AudioRingBuffer do bezpiecznego niwelowania transmisji audio pomiędzy gwintem przechwytywania i inferencji.
SystemAudioTap
Odtwarza mix wyjściowy systemu - co obecnie gra Mac - jako mono Float32 przy częstotliwości próbkowania wybranej przez telefon (krany procesowe Core Audio, macOS 14.4+). Domyślny, globalny kran mononowy wyłącza bieżący proces, tak więc odtwarzanie własnej aplikacji nigdy nie jest ponownie przechwytywane i jest owijane w prywatne urządzenie agregatowe zawierające tylko kran.
let tap = SystemAudioTap()
try tap.start(targetSampleRate: 16000) { samples in
pipeline.pushAudio(samples)
}
tap.stop()
Przechwytywanie przetrwa wyłączniki default- output- device - wewnętrzny resampler jest przebudowywany, gdy zmienia się szybkość mieszania. Aplikacja hosta musi zadeklarować NSAudioCaptureUsageDescriptionJeśli pozwolenie na nagrywanie nie zostanie wydane, można jeszcze utworzyć kran, który zapewni tylko ciszę: zegarek framesCaptured rosną podczas nonSilentFrames pozostaje 0 i nie powiodło się.
Terminowe źródła niezależne
Obie klasy przechwytywania ujawniają przekroczenie czasu. CapturedAudioChunk ma mono samples at sampleRate plus opcjonalny Mach hostTime dla pierwszej ramki wejściowej. Ponieważ oba źródła używają tego samego zegara hosta, aplikacja może zachować swoje PCM oddzielone i zamówić otrzymane zdarzenia transkrypcyjne bez mieszania dźwięku.
try tap.startTimestamped(targetSampleRate: 16000) { systemChunk in
systemPipeline.pushAudio(systemChunk.samples)
recordSystemTime(systemChunk.hostTime)
}
let microphone = AudioIO(enableAEC: true, enablePlayback: false)
try microphone.startMicrophoneTimestamped(targetSampleRate: 16000) { micChunk in
microphonePipeline.pushAudio(micChunk.samples)
recordMicrophoneTime(micChunk.hostTime)
}
Tylko dla słuchaczy, enableAEC: true aktywuje przetwarzanie głosu Apple przed odczytem formatu mikrofonu, oraz enablePlayback: false pomija nieużywany odtwarzacz. Na macOS odtwarzanie urządzenia jest usuwane z próbek mikrofonów i brak uruchomienia AEC jest zgłaszany zamiast wycofywania się do surowego wejścia.
Incremental file input and decognition
CapturedAudioChunk posiada również monotonię frameIndex oraz isFinal marker. AudioFileLoader.stream produkuje te kawałki na żądanie z ograniczoną pamięcią, resempuje raz z trwałym stanem konwertera i domyślnie średnio wszystkie kanały wejściowe. Użyj .first lub .select([indices]) gdy wiadomo o routingu kanału.
StreamingRecognitionModel oraz StreamingRecognitionSession zapewnić jeden zapasowy zapasowy API w całym strumieniu Parakeet i Nemotron Core ML / MLX:
let source = AudioFileLoader.stream(
url: inputURL,
options: AudioFileStreamOptions(targetSampleRate: 16_000,
chunkDuration: 0.32,
channelSelection: .mixAll))
let session = try model.makeStreamingSession(language: "en-US")
for try await chunk in source {
for update in try session.push(chunk) {
render(update.text, final: update.isFinal)
}
}
for update in try session.finish() {
render(update.text, final: true)
}
SentencePieceModel
Wspólny czytnik protobuf dla SentencePiece .model pliki, mieszka w AudioCommon. Każdy moduł, który musi dekodować elementy SentencePiece (PersonaPlex, OmnilingualaSR, przyszłe porty ASR / TTS) buduje swój własny dekoder na górze tego pojedynczego czytnika zamiast reimplementowania formatu drutu protobuf.
public struct SentencePieceModel: Sendable {
public struct Piece: Sendable, Equatable {
public let text: String
public let score: Float
public let type: Int32
public var pieceType: PieceType? { get }
public var isControlOrUnknown: Bool { get }
}
public enum PieceType: Int32 {
case normal = 1, unknown = 2, control = 3,
userDefined = 4, unused = 5, byte = 6
}
public let pieces: [Piece]
public var count: Int { get }
public subscript(_ id: Int) -> Piece? { get }
public init(contentsOf url: URL) throws
public init(modelPath: String) throws
public init(data: Data) throws
}
Używane przez: OmnilingualASR.OmnilingualVocabulary, PersonaPlex.SentencePieceDecoder. Objęte badaniami jednostkowymi 7 w Tests/AudioCommonTests/SentencePieceModelTests.
MLXEY.SDPA
Skalowane dot-produkt pomocników uwagi wspólne w każdym MLX moduł uwagi (Qwen3-ASR / Qwen3-TTS / Qwen3-Chat / CosyVoice / PersonaPlex / OmnilingualaASR). Każdy moduł zachowuje swoje własne projekcje - SDPA tylko obsługuje reshape → uwaga → połączenie płyty boilerplate.
public enum SDPA {
// Flat [B, T, H*D] input: project/reshape happens inside
public static func multiHead(
q: MLXArray, k: MLXArray, v: MLXArray,
numHeads: Int, headDim: Int, scale: Float,
mask: MLXArray? = nil
) -> MLXArray
// GQA / MQA variant with separate query and KV head counts
public static func multiHead(
q: MLXArray, k: MLXArray, v: MLXArray,
numQueryHeads: Int, numKVHeads: Int, headDim: Int, scale: Float,
mask: MLXArray? = nil
) -> MLXArray
// Already-shaped [B, H, T, D] (RoPE / KV cache paths)
public static func attendAndMerge(
qHeads: MLXArray, kHeads: MLXArray, vHeads: MLXArray,
scale: Float,
mask: MLXArray? = nil
) -> MLXArray
// Same, with ScaledDotProductAttentionMaskMode enum (newer API)
public static func attendAndMerge(
qHeads: MLXArray, kHeads: MLXArray, vHeads: MLXArray,
scale: Float,
mask: MLXFast.ScaledDotProductAttentionMaskMode
) -> MLXArray
// Low-level head merge: [B, H, T, D] → [B, T, H*D]
public static func mergeHeads(_ attn: MLXArray) -> MLXArray
}
Wszystkie rozmowy przeobraźliwe korzystają -1 dla wymiaru wsadowego, więc pomocnicy składają się z MLX.compile(shapeless:) wykresy, które różnią się partią w czasie biegu (np. Qwen3-TTS Talker autoregresywny dekod).
Serwer HTTP API
W speech-server binarne eksponuje każdy model w speech-swift jako punkty końcowe HTTP REST plus punkt końcowy WebSocket, który implementuje OpenAI Realtime API. Modele są ładowane lazily na pierwsze życzenie; przejść --preload Ogrzewać je wszystkie przy starcie.
swift build -c release
.build/release/speech-server --port 8080
# Preload every model at startup
.build/release/speech-server --port 8080 --preload
Punkty końcowe REST
| Punkt końcowy | Metoda | Wniosek | Odpowiedź |
|---|---|---|---|
/transcribe | POST | audio/wav ciało | JSON { text } (Qwen3-ASR) |
/v1/audio/transcriptions | POST | Wieloczęściowy { file, model, response_format?, language?, prompt?, temperature? } | JSON, tekst, verbose JSON lub VTT |
/v1/audio/speech | POST | JSON { model, input, voice, response_format?, speed? } | audio/wav lub audio/pcm ciało |
/speak | POST | JSON { text, engine?, language?, voice? } | audio/wav korpus (Qwen3-TTS, CosyVoice i Kokoro) |
/respond | POST | audio/wav ciało | audio/wav PersonaPlex) |
/enhance | POST | audio/wav ciało | audio/wav DeepFilterNet3) |
/vad | POST | audio/wav ciało | Lista segmentów JSON |
/diarize | POST | audio/wav ciało | JSON DiarizedSegment wykaz |
/embed-speaker | POST | audio/wav ciało | JSON [Float] 256- dim |
/v1/audio/speech maps standard TTS model i nazwy głosowe do lokalnych silników. Domyślnie WAV; response_format: "pcm" zwraca beznagłówkowy dźwięk 24 kHz mono PCM16 little- endian. Nieobsługiwane skompresowane formaty są wyraźnie odrzucane.
/v1/audio/transcriptions akceptuje jeden plik WAV jako wieloczęściowy plik tworzący dane i domyślnie {"text":"..."}; response_format może również poprosić tekst, verbose JSON, SRT, lub VTT. Na Apple, aliasy modelu wybrać lokalne ASR silnik. Opakowanie speech-core serwer włączony Linux oraz Windows wykorzystuje lazily loaded Parakeet, akceptuje PCM16/24/32 lub Float32 WAV, i zatyczki do wysyłania w 25 MiB oraz 10 Minuty.
# Transcribe a file
curl -X POST http://localhost:8080/transcribe \
--data-binary @recording.wav \
-H "Content-Type: audio/wav"
# Synthesize speech
curl -X POST http://localhost:8080/speak \
-H "Content-Type: application/json" \
-d '{"text": "Hello world", "engine": "cosyvoice"}' \
-o output.wav
# OpenAI-compatible transcription
curl http://localhost:8080/v1/audio/transcriptions \
-F "[email protected];type=audio/wav" \
-F "model=whisper-1"
# OpenAI-compatible speech synthesis
curl http://localhost:8080/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{"model":"tts-1","voice":"alloy","input":"Hello world","response_format":"wav"}' \
-o openai-output.wav
# Full speech-to-speech round trip
curl -X POST http://localhost:8080/respond \
--data-binary @question.wav \
-o response.wav
OpenAI Realtime API/v1/realtime)
WebSocket ws://host:port/v1/realtime implementuje OpenAI Protokół Realtime. Wszystkie wiadomości są JSON z type Dyskryminator; ładunki audio są base64-encoded PCM16 w 24 kHz mono.
Podczas obciążenia modelu zimnego lub długich generacji, serwer emituje lekkie realtime.keepalive Wydarzenia JSON i ramki sterowania pong websocket w przybliżeniu co 15 sekund do momentu, aż model będzie gotowy. Klienci mogą ignorować te zdarzenia lub traktować je jako wskaźniki aktywności.
Klient → Zdarzenia serwera
| Zdarzenie | Cel |
|---|---|
session.update | Konfiguracja silnika, języka, głosu, formatu audio i opcjonalnego serwera VAD |
input_audio_buffer.append | Dołącz element base64 PCM16 do bufora wejściowego |
input_audio_buffer.commit | Wywołaj buforowany dźwięk do transkrypcji |
input_audio_buffer.clear | Wyrzuć bieżący bufor wejściowy |
response.create | TTS synteza dla dostarczonego tekstu / instrukcji |
Serwer → Wydarzenia klientów
| Zdarzenie | Znaczenie |
|---|---|
session.created | Uścisk dłoni kompletny, domyślny edytowany config |
session.updated | Najnowsze session.update potwierdzony |
input_audio_buffer.speech_started | Serwer VAD wykrył początek obrotu mowy |
input_audio_buffer.speech_stopped | Serwer VAD zakończył zakręt po cichu lub limitu czasu trwania |
input_audio_buffer.committed | Audio akceptowane i w kolejce do transkrypcji |
conversation.item.input_audio_transcription.completed | Wynik ASR z końcowym tekstem transkrypcji |
response.audio.delta | Base64 PCM16 kawałek zsyntetyzowanego dźwięku |
response.audio.done | Brak fragmentów audio dla tej odpowiedzi |
response.done | Odpowiedź finalizowana (metadane + statystyki dotyczące opóźnień) |
error | Koperta błędów z type oraz message |
Automatyczne zakręty z serwerem VAD
Wyrażenie input_audio_buffer.commit pozostaje domyślny. Aby serwer został zamknięty i przepisywany automatycznie z Silero VAD, skonfigurować turn_detection. Serwer przechowuje tylko wymagane prerolla podczas bezczynności, emituje mowę start / stop i commit events w porządku, a force- zamyka ciągły aktywny obrót na max_turn_duration_ms.
ws.send(JSON.stringify({
type: 'session.update',
session: {
turn_detection: {
type: 'server_vad',
threshold: 0.5,
prefix_padding_ms: 300,
silence_duration_ms: 500,
max_turn_duration_ms: 120000
}
}
}));
// Disable automatic turns and return to explicit commits:
// { type: 'session.update', session: { turn_detection: null } }
const ws = new WebSocket('ws://localhost:8080/v1/realtime');
// ASR: push audio, request transcription
ws.send(JSON.stringify({ type: 'input_audio_buffer.append', audio: base64PCM16 }));
ws.send(JSON.stringify({ type: 'input_audio_buffer.commit' }));
// → conversation.item.input_audio_transcription.completed
// TTS: request synthesis and stream audio deltas
ws.send(JSON.stringify({
type: 'response.create',
response: { modalities: ['audio', 'text'], instructions: 'Hello world' }
}));
// → response.audio.delta (repeated), response.audio.done, response.done
Serwer mieszka w AudioServer Produkt SPM. Przykładowy klient przeglądarki jest wysyłany na Examples/websocket-client.html - otworzyć go obok uruchomionego serwera, aby prowadzić pełną podróż w obie strony ASR + TTS.
Pobieranie modeli
Wszystkie modele są pobierane z HuggingFace przy pierwszym użyciu i buforowane w ~/Library/Caches/qwen3-speech/. AudioCommon moduł zapewnia współdzielone HuggingFaceDownloader który obsługuje pobieranie, buforowanie i weryfikację integralności.
Pobieranie powtórzeń
Ładowarki modelu i pobrać API akceptuj downloadOptions: DownloadOptions. onRetry otrzymuje DownloadRetryEvent przed każdym wycofaniem, modelId, attempt, maxAttempts, oryginał errororaz retryInSeconds. nil oznacza, że nie ma powtórzeń; zapas pamięci podręcznej może się jeszcze udać.
Załączenia są synchroniczne i @Sendable, bez gwarancji głównego aktora. Anulowanie nie powoduje zdarzenia retry. retryDelaysSeconds dostosować politykę ([] oznacza jedną próbę); pierwszeństwo ma wyraźny argument pobierania API. Domyślna liczba prób i postęp bajtów są zachowane. AudioLog.download.
Anulowanie współpracy
dla transkrypcji MLX, Qwen3ASRModel.transcribeCheckingCancellation(audio:sampleRate:options:) rzuty CancellationError gdy anulowanie zadania jest obserwowane przed wydobyciem funkcji, kodowaniem, przedrostkiem dekodera lub krokiem dekodującym. GPU prace już złożone może zakończyć. Synchroniczne transcribe oraz transcribeBatch API zachowują swoje nierzucające zachowanie i ignorują anulowanie zadania. speech-server używa punktu wejścia dla Qwen3-ASR, który jest świadomy anulowania.
MOSS- TTS- Nano
MossTTSNANOModel serializuje żądania i tworzy świeży stan CoreML dla każdego wyniku. syntetyzuj i syntetyzuj Stream zachować stereo; SpeechGenerationModel zwraca mono downmix. Pass language: zero. Streams kończy się z pustym znacznikiem końcowym.
import AudioCommon
import MossTTSNano
let model = try await MossTTSNanoModel.fromPretrained()
let reference = try await model.encodeReference(url: referenceURL)
let result = try await model.synthesize(
text: "The weather is nice today.", reference: reference,
options: MossTTSNanoOptions(maxFrames: 300, seed: 1))
try WAVWriter.writeStereo(left: result.left, right: result.right,
sampleRate: 48000, to: outputURL)Lokalne zgłoszenie rodzime
Dla 12B, powtarzające się przyległe znaczniki zamykające wywołanie narzędzi natychmiast po zakończeniu, zwalidowanego połączenia są konsumowane z tym wywołaniem. Nie tworzą one innego wywołania ani nie zmieniają jego argumentów. Markery standalone i zniekształcone argumenty pozostają błędami; E4B zachowuje ścisłą obsługę.
Aby zachować nieudany natywny chat, ustaw failureCaptureDirectory w manifeście referencyjnym. Domyślnie jest wyłączona przechwytywanie. Katalog musi odmówić dostępu grupowego i innego; nowe katalogi używają 0700 i plików błędów używać 0600. Pliki zawierają dokładny organ żądania, ID wniosku i błąd. Ukończone generacje 12B zachowują również renderowany sygnał, token ID, wyjściowe i stop przed parsowaniem. Niepowodzenia E4B nie mają surowego zapisu generacji. Te pliki lokalne zawierają zawartość rozmowy i narzędzia; błędy HTTP i udane żądania nie zawierają ich.
Gemma4UnifiedChat.generateResponse akceptuje fakultatywny onGeneration z Gemma4GenerationTrace. Wywołanie działa przed parsowaniem narzędzia, w tym po odrzuceniu jego zakończenia. Nagrywanie nie zmienia pobierania próbek lub zaakceptowanej składni narzędzia.
Fakultatywny agentDirectory ładuje ustawione Gemma 4 12B Unifikacja INT4 model tekstowy obok E4B. Użyj modelu: "gemma4-12b-it"dla czatu z natywnymi narzędziami, historii wyników i anulowania. E4B pozostaje dostępna dla innych wniosków. Gemma4UnifiedChat wykorzystuje przypinane MLX implementacja tekstu i uwalnia każdy podręcznik KV. Parsing odpowiedzi usuwa separatory kanałów z zwróconego tekstu. Osierocone terminatory wywołania narzędzi są odrzucane jako nieprawidłowo utworzone odpowiedzi. Gemma 4 12B, zawartość wiadomości narzędziowej zachowuje JSON jako tekst, dopasowując szablon czatu punktu kontrolnego.
Budowy źródeł akceptują --inference-manifest models.json do wczytania ustawione Gemma 4 E4B INT4, VoxCPM2, pełny Whisper Large-v3oraz Silero VADUsługa ta rozpoczyna się w trybie offline i odrzuca niekompatybilne tożsamości modelu.
VoxCPM2 obsługuje ważenie Precision: .float16 dla niezliczonego punktu kontrolnego. Główne parametry tego punktu wykorzystują FP16; AudioVAE i niektóre obliczenia pozostają FP32. Punkty kontrolne INT8 są odrzucane dla tego trybu.
Gemma4ConversationMessage, Gemma4Tool i generateResponse zachować rodzime deklaracje narzędzi, argumenty, i historię wyników. InferenceCancellationToken anuluje generację kooperacyjnie. Silero makeindependent Stream izoluje stan, a procesChunkThrowing propaguje błędy wnioskowania.
Whisper aSRModel.from Ładunki katalogowe wystawione pełne pakiety Large-v3 CoreML. Dekoder z warstwy 32- może zbudować przedrostek bez TextDecoderContextPreferill.mlmodelc i uwalnia perstep temporaria. Domyślny model Whisper CLI pozostaje Turbo.
Native service serializes chat, synteza i transkrypcja; lekki VAD ma oddzielny stan strumienia. Gemma prefill używa serii 256- token, żądanie stanu KV jest zwolniony, a pamięci podręcznej MLX jest ograniczona do 128 MiB z limitem alokatora 20 GiB.
Przemowa ze strumieniem: True zwraca zdarzenia audio NDJSON w 48 kHz, po którym następuje wykonanie lub błąd. Anuluj requestID na /v1/ wniosek / anulować. GET /v1/ wnioskowanie / możliwości i /v1/ inference / prience report model identifies, parametr dtypes, asset pin, and memory counters.
speech-server --inference-manifest models.json --port 8080POST /v1/inference
{"operation":"chat","model":"gemma4-e4b-it","requestID":"chat-1","messages":[{"role":"user","content":"Hello"}],"maxTokens":128,"temperature":0}
chat: gemma4-e4b-it
speech: voxcpm2
transcribe: whisper-large-v3
vad: silero-vad
Audio: audioBase64 (mono PCM16), sampleRate
VAD: streamID, reset, endStream; 512 samples/frame at 16000 Hz
Tools: name, description, parameters
Tool call: id, name, arguments
Tool result: role="tool", toolCallID, contentOpcjonalny model własności
SpeechRuntime zapewnia wspólne ładowanie, anulowalne wstęp FIFO i seryjne wykonanie. Qwen3ASRRuntime wybiera MLX lub pełne CoreML za pomocą wyraźnych deskryptorów; krajowe API i flagi CLI pozostają dostępne.
Serwer audio posiada jedno zezwolenie na wykonanie za pomocą każdego pełnego żądania, w tym wyjścia strumieniowego, z żądaniami do 32 oczekujących. Niepowodzenia ASR powodują błędy HTTP lub zdarzenia błędów WebSocket; prawidłowe puste transkrypcje pozostają skuteczne. Odtwarzanie zachowuje próbki przepełnienia.
import Qwen3ASRRuntime
let runtime = Qwen3ASRRuntime(descriptor: Qwen3ASRDescriptors.small)
let text = try await runtime.transcribe(audio: samples, sampleRate: 16000)
try await runtime.unload()Voxtral TTS
Voxtral 4B TTS 2603 działa lokalnie Apple Silicon przez moduł Voxtral TTS: 20 preset voices, nine languages, and 24 KHz mono mowy.
import VoxtralTTS
let model = try await VoxtralTTSModel.fromPretrained()
for try await chunk in model.generateStream(text: "Hello, welcome to Voxtral.", voice: "casual_male") {
print(chunk.frameIndex, chunk.samples.count, chunk.isFinal)
}Wyłącznie głosy preset; klonowanie referencji audio jest niedostępne. Strumienie wyjściowe audio przez SDK, CLI, WebSocket i HTTP PCM. Dostarczają kompletny tekst wejściowy z przodu. Wagi modelu używają CC BY- NC 4.0.
import AudioCommon
let player = StreamingAudioPlayer()
player.skipInitialSilence = false