Traducere automată · Originalul în engleză
API și protocoale
Nativ Apple runtime · PocketTTS
PocketTTS adaugă sinteze native CoreML sinteze de vorbire la speech-swift. Toate etapele sunt implicite pentru executarea exclusivă a procesorului. Fluxurile de prefixe engleze Alba sunt 1,920-frame de probe la 24 kHz (80 ms per cadru).
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)
} Modulul AudioCommon definește protocoalele de model-agnostic și tipurile partajate. Orice model de conformitate poate fi utilizat interschimbabil prin intermediul acestor interfețe.
Prezentare generală a protocolului
┌─────────────────────────────────────────────────────────┐
│ AudioCommon │
│ │
│ AudioChunk SpeechGenerationModel (TTS) │
│ AlignedWord SpeechRecognitionModel (STT) │
│ SpeechSegment ForcedAlignmentModel │
│ SpeechToSpeechModel │
│ VoiceActivityDetectionModel (VAD) │
│ TurnCompletionProvider (EOT) │
│ SpeakerEmbeddingModel │
│ SpeakerDiarizationModel │
│ SpeakerExtractionCapable │
└─────────────────────────────────────────────────────────┘Mod de vorbireRecunoaștere
Protocolul pentru modelele de vorbire în text.
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
}
Tipuri de formulare: Qwen3ASRModel, WhisperASRModel, ParakeetASRModel, ParakeetStreamingASRModel, OmnilingualASRModel (CoreML), OmnilingualASRMLXModel (MLX)
Mod de vorbire
Protocolul pentru modelele de text-la-discurs.
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() are o implementare implicită care se încheie generate() ca o singură bucată. Modele cu streaming adevărat (de exemplu, Qwen3-TTS) suprascrie.
Tipuri de formulare: Qwen3TTSModel, CosyVoiceTTSModel, VoxCPM2TTSModel, KokoroTTSModel, IndexTTS2TTSModel
IndexTTS2TTSModel adaugă o referință-audio generate supraîncărcare pentru clonarea vocală cu foc zero și folosește IndexTTS2SynthesisOptions pentru controlul vitezei de vorbire și de pauză.
Qwen3-TTS încărcare locală
fromLocal validează fișierele necesare și caracterul complet al punctului de control, apoi derivă dimensiunea modelului, cuantizarea și arhitectura de la config.json. Valoarea opțională configuration este doar o rezervă pentru metadate omise; metadate malefice sau contradictorii aruncă Qwen3TTSLoadingError.invalidConfiguration înainte de alocarea modelului.
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 implicits to .none, astfel încât nu schimbă procesul la nivel Metal wired-memory limit. fromPretrained păstrează .pin(fraction: 0.9) implicit.Ambii încărcători precalificați acceptă un separat tokenizerCacheDir.
Mod de aliniere forţată
Protocolul pentru alinierea la nivelul de timp a marcajelor de caractere.
public protocol ForcedAlignmentModel: AnyObject {
func align(audio: [Float], text: String, sampleRate: Int, language: String?) -> [AlignedWord]
}
DiscursPentru DiscursModel
Protocolul pentru modelele de dialog între vorbire și vorbire.
public protocol SpeechToSpeechModel: AnyObject {
var sampleRate: Int { get }
func respond(userAudio: [Float]) -> [Float]
func respondStream(userAudio: [Float]) -> AsyncThrowingStream<AudioChunk, Error>
}
Tipuri de formulare: PersonaPlexModel
HibikiTraduce Model
Translator de vorbire-la-discurs. SpeechToSpeechModel deoarece contractul este diferit: intrarea și producția sunt limbi diferite, lungimea de ieșire este variabilă (orientată EOS, plafonată la max(tSrc * 5/2, tSrc + 20) pași 1.0–1.6× durata sursei pe clipuri de tip FLEURS), și există un flux paralel text-token alături de audio. Ghid complet →
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
}
Rata maximă: 24 kHz mono, atât de intrare, cât și de ieșire. Rată de bază: 12,5 Hz (Mimi codec). Limbi sursă: FR / ES / PT / DE. Limbă de țintă: EN. Set HIBIKI_GREEDY=1 la lansarea procesului pentru decodarea argmax deterministic (matches the CI canaries).
Modul de activare vocală
Protocol pentru detectarea activității vocale.
public protocol VoiceActivityDetectionModel: AnyObject {
var inputSampleRate: Int { get }
func detectSpeech(audio: [Float], sampleRate: Int) -> [SpeechSegment]
}
Completare de rotire
Protocolul pentru clasatorii finali consultați de StreamingVADProcessor pe fiecare pauza VAD confirmată. A VAD aude doar tăcere; un model de turn-completare ascultă prosodia întregii declaraţii, astfel încât o pauză de sentinţă medie îl face pe agent să aştepte în timp ce o propoziţie terminată primeşte un răspuns imediat. Hărţile către speech-core sc_turn_completion_vtable_t Vezi... Ghid 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
}
Tipuri de formulare: SmartTurnModel (Smart Turn v3.2)
VorbitorEmbeddingModel
Protocolul pentru introducerea de către vorbitor a extracției.
public protocol SpeakerEmbeddingModel: AnyObject {
var inputSampleRate: Int { get }
var embeddingDimension: Int { get }
func embed(audio: [Float], sampleRate: Int) -> [Float]
}
Tipuri de formulare: WeSpeakerModel
Mod de diagramă
Protocol pentru modele de diarizare difuzor care atribuie etichete difuzor segmente audio.
public protocol SpeakerDiarizationModel: AnyObject {
var inputSampleRate: Int { get }
func diarize(audio: [Float], sampleRate: Int) -> [DiarizedSegment]
}
Tipuri de formulare: DiarizationPipeline (Pyannote), SortformerDiarizer
Extragere difuzorCapabil
Protocolul extins de diarizare pentru motoare care suportă extragerea segmentelor unui vorbitor țintă folosind o înglobare de referință. Nu toate motoarele susțin acest lucru (Sortformer este de la un capăt la altul și nu produce înglobări difuzor).
public protocol SpeakerExtractionCapable: SpeakerDiarizationModel {
func extractSpeaker(audio: [Float], sampleRate: Int, targetEmbedding: [Float]) -> [SpeechSegment]
}
Tipuri de formulare: DiarizationPipeline (Pyannote numai)
Tipuri partajate
AudioChunk
public struct AudioChunk {
public let samples: [Float] // PCM samples
public let sampleRate: Int // Sample rate (e.g. 24000)
}
Segment de vorbire
public struct SpeechSegment {
public let startTime: Float // Start time in seconds
public let endTime: Float // End time in seconds
}
Cuvântul aliniat
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
}
Segment diarizat
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)
}
DialogSegment
Un segment parsat de dialog multi-speaker text cu difuzor opţional şi etichete emoţionale. Utilizat cu DialogueParser şi DialogueSynthesizer pentru CosyVoice3 sinteza dialogului.
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
}
DialogParser
Parses multi-speaker dialog text cu tag-uri inline speaker ([S1]) și etichete emoționale ((happy)).
public enum DialogueParser {
static func parse(_ text: String) -> [DialogueSegment]
static func emotionToInstruction(_ emotion: String) -> String
}
Emoţii construite: happy/excited, sad, angry, whispers/whispering, laughs/laughing, calm, surprised, serious Etichetele necunoscute trec prin ca instrucțiuni de formă liberă.
DialogSintezator
Orchestrates multi-segment dialog sinteză cu clonarea vocii per-speaker, goluri de tăcere, și crossfade.
public enum DialogueSynthesizer {
static func synthesize(
segments: [DialogueSegment],
speakerEmbeddings: [String: [Float]],
model: CosyVoiceTTSModel,
language: String,
config: DialogueSynthesisConfig,
verbose: Bool
) -> [Float]
}
DialogSintezăConfig
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
}
Pipellm
Protocolul pentru integrarea modelului lingvistic cu conductele de voce. Poduri o LLM la VoicePipeline's ASR → LLM → TTS Curge.
public protocol PipelineLLM: AnyObject {
func chat(messages: [(role: MessageRole, content: String)],
onToken: @escaping (String, Bool) -> Void)
func cancel()
}
Adaptor de alimentare: Qwen3PipelineLLM poduri Qwen35MLXChat la acest protocol cu curăţare jeton, anulare şi acumulare de fraze în aşteptare.
AudioIO
Managerul audio reutilizabil I/O care elimină cazaniera AVAudioEngine. Manipulează captura mic, resampling, redare, și contorizarea nivelului audio.
let audio = AudioIO()
try audio.startMicrophone(targetSampleRate: 16000) { samples in
pipeline.pushAudio(samples)
}
audio.player.scheduleChunk(ttsOutput)
audio.stopMicrophone()
AudioIO include un StreamingAudioPlayer pentru o ieșire TTS și un AudioRingBuffer pentru un transfer audio securizat între fire de captare și de interferență.
SystemAudioTap
Captureaza mixul de iesire al sistemului, care este Mac care joaca in prezent ca mono Float32 la o rata de esantionare calumer-chosen (Core Audio procesting robinets, macOS 14.4+). Doar robinetul mono global exclude procesul curent implicit, astfel încât o aplicație propria redare nu este niciodată re-capturată, și este învelit într-un dispozitiv agregat privat care conține doar robinetul.
let tap = SystemAudioTap()
try tap.start(targetSampleRate: 16000) { samples in
pipeline.pushAudio(samples)
}
tap.stop()
Captura supravieţuieşte switch-urilor de ieşire implicită NSAudioCaptureUsageDescription. În cazul în care permisiunea de înregistrare este respinsă, un robinet poate fi creat încă livra doar tăcere: ceas framesCaptured cresc în timp ce nonSilentFrames rămâne 0 și nu reușesc închis.
Surse independente cu timbru temporizat
Ambele clase de captare expun suprasarcini cu timbru temporal. CapturedAudioChunk poartă mono samples la sampleRate plus un Mach opțional hostTime pentru primul cadru de intrare. Deoarece ambele surse folosesc același ceas gazdă, o aplicație poate păstra evenimentele de transcriere derivate din PCM separate și ordine fără a amesteca audio.
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)
}
Pentru captarea numai de ascultare, enableAEC: true activează Apple Voice Processing înainte ca formatul microfonului să fie citit, și enablePlayback: false omite jucătorul neutilizat. Pe macOS, redarea dispozitivului este eliminată din mostrele microfonului și se raportează eșecul de pornire a AEC în loc să se retragă la intrare brută.
Intrarea și recunoașterea inițială a fișierelor
CapturedAudioChunk are, de asemenea, un monoton frameIndex și un marker isFinal. AudioFileLoader.stream produce aceste bucăți la cerere cu memorie delimitată, reeșalonări o dată cu starea persistentă a convertorului și media tuturor canalelor de intrare în mod implicit.Use .first sau .select([indices]) atunci când ruta canalului este cunoscută.
StreamingRecognitionModel și StreamingRecognitionSession provide o cache-conserving incremental API through Parakeet Streaming and 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)
}
SentincePieceModel
Cititor de protobuf partajat pentru SentincePiece .model fișiere, trăiește în AudioCommon. Fiecare modul care trebuie să decodeze piese de SentencePiece (PersonaPlex, OmnilingualASR, viitor ASR / TTS porturi) își construiește propriul decodor pe partea de sus a acestui singur cititor în loc să reimplementeze formatul de sârmă 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
}
Utilizat de: OmnilingualASR.OmnilingualVocabulary, PersonaPlex.SentencePieceDecoder. Acoperit de 7 teste unitare în Tests/AudioCommonTests/SentencePieceModelTests.
MLXCommon.SDPA
Atentie punct-produs escaladat ajutor partajat pe fiecare modul de atenţie MLX (Qwen3-ASR / Qwen3-TTS/Qwen3-Chat // CosyVoice & PersonaPlex - OmnilingualASR). Fiecare modul păstrează propriile proiecţii
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
}
Toate apelurile de remodelare utilizează -1 pentru dimensiunea lotului, astfel încât ajutoarele să compună cu MLX.compile(shapeless:) grafice care să varieze lotul la rulare (de exemplu Qwen3-TTS Talker decodare autoregresivă).
HTTP API Server
ă speech-server binara expune fiecare model din speech-swift sub forma unor criterii finale HTTP REST plus un criteriu final WebSocket care pune în aplicare OpenAI Realtime API. Modelele sunt încărcate lene la prima cerere; trece --preload să le încălzească pe toate la startup.
swift build -c release
.build/release/speech-server --port 8080
# Preload every model at startup
.build/release/speech-server --port 8080 --preload
REST Puncte finale
| Punct final | Metoda | Cerere | Răspuns |
|---|---|---|---|
/transcribe | POST | audio/wav organism | JSON { text } (Qwen3-ASR) |
/v1/audio/transcriptions | POST | Multipart { file, model, response_format?, language?, prompt?, temperature? } | JSON, text, verbose JSON , SRT sau VTT |
/v1/audio/speech | POST | JSON { model, input, voice, response_format?, speed? } | audio/wav sau audio/pcm organism |
/speak | POST | JSON { text, engine?, language?, voice? } | audio/wav corp (Qwen3-TTS, CosyVoice și Kokoro) |
/respond | POST | audio/wav organism | audio/wav organism (PersonaPlex) |
/enhance | POST | audio/wav organism | audio/wav corp (DeepFilterNet3) |
/vad | POST | audio/wav organism | Lista segmentului JSON |
/diarize | POST | audio/wav organism | JSON DiarizedSegment list |
/embed-speaker | POST | audio/wav organism | JSON [Float] (256-dim) |
/v1/audio/speech hărţi standard TTS modele şi denumiri vocale pentru motoarele locale. Implică la WAV; response_format: "pcm" întoarce fără antet 24 kHz mono PCM16 audio mic-endian. Formatele comprimate nesusţinute sunt respinse în mod explicit.
/v1/audio/transcriptions acceptă un fișier WAV ca date multipart sub formă și implicit la {"text":"..."}; response_format poate solicita, de asemenea, text, verbose JSON, SRT, sau VTT. Pe Apple, modele pseudonime selectați un local ASR Motorul. speech-core server pe server Linux şi Windows foloseste lenes încărcat Parakeet, acceptă PCM16/24/32 sau Float32 WAV, și capace de încărcare la 25 MiB și 10 minute.
# 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)
Obiectivul WebSocket la ws://host:port/v1/realtime pune în aplicare protocolul OpenAI Realtime. Toate mesajele sunt JSON cu un discriminatoriu type; încărcăturile utile audio sunt base64-encoded PCM16 la 24 kHz mono.
In timpul incarcarilor de modele reci sau generatii lungi, serverul emite usor realtime.keepalive Evenimente JSON si rame de control pong websocket aproximativ la fiecare 15 secunde pana cand modelul de iesire este gata. Clientii pot ignora aceste evenimente sau le trateaza ca indicatori de activitate.
Client → Evenimente server
| Eveniment | Scop |
|---|---|
session.update | Configurează motorul, limba, vocea, formatul audio și serverul opțional VAD |
input_audio_buffer.append | Adăugați o bucată base64 PCM16 la tamponul de intrare |
input_audio_buffer.commit | Comite audio tamponat pentru transcriere |
input_audio_buffer.clear | Aruncați tamponul de intrare curent |
response.create | Cerere TTS sinteză pentru textul/instrucțiunile furnizate |
Server → Evenimente client
| Eveniment | Semnificație |
|---|---|
session.created | Strângere de mână completă, configurare implicită emisă |
session.updated | Cele mai recente session.update recunoscute |
input_audio_buffer.speech_started | Server VAD detectat începutul unui turn de vorbire |
input_audio_buffer.speech_stopped | Server VAD a încheiat rotirea după tăcere sau limita de durată |
input_audio_buffer.committed | Audio acceptat și la coadă pentru transcriere |
conversation.item.input_audio_transcription.completed | ASR rezultatul cu textul transcrierii finale |
response.audio.delta | Base64 PCM16 bucată de audio sintetizat |
response.audio.done | Nu mai multe bucăți audio pentru acest răspuns |
response.done | Răspuns finalizat (metadate + statistici privind latența) |
error | Anvelopă de eroare cu type și message |
Rânduri automate cu server VAD
Explicit input_audio_buffer.commit rămâne implicit. Pentru a lăsa serverul să se închidă și transcrie automat cu Silero VAD, configura turn_detection. Serverul păstrează doar pre-roll-ul solicitat în timp ce inactiv, emite start/stop și comit evenimente în ordine, și se închide continuu activ la 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
Serverul locuiește în produsul AudioServer SPM. Un client de tip browser de exemplu este expediat la Examples/websocket-client.html deschideți-l alături de un server care rulează pentru a conduce întreaga călătorie ASR + TTS rotundă.
Modele de descărcări
Toate modelele sunt descărcate de la HuggingFace la prima utilizare și cache în ~/Library/Caches/qwen3-speech/. Modulul AudioCommon oferă un partaj HuggingFaceDownloader care se ocupă de descărcare, caching și verificarea integrității.
Descărcați retries
Modele de incarcatoare si descarca API accepta downloadOptions: DownloadOptions. onRetry a primit DownloadRetryEvent înainte de fiecare retragere, cu modelId, attempt, maxAttempts, originalul error, și retryInSeconds. nil înseamnă că nu mai rămân retensiuni; retragerea completă a cache-ului poate reuși.
Retragerile sunt sincrone și @Sendable, fără o garanție principală. Anularea nu produce un eveniment de retrie. Set retryDelaysSeconds pentru a personaliza politica ([] înseamnă o singură încercare; descărcarea explicită API argument are prioritate. Se păstrează numărul de încercări implicite și progresele octet. Încercările eșuate sunt înregistrate și la nivel de notificare prin AudioLog.download.
Anulare cooperativă
Pentru MLX transcription, Qwen3ASRModel.transcribeCheckingCancellation(audio:sampleRate:options:) aruncări CancellationError atunci când anularea sarcinii este observată înainte de extragerea caracteristicilor, codificarea, prefillarea decodorului sau o etapă de decodare. GPU munca depusă deja poate fi terminată. Cronhous transcribe și transcribeBatch API își păstrează comportamentul de non-ascensiune și ignoră anularea sarcinilor. speech-server folosește punctul de intrare pentru anulare-aware pentru Qwen3-ASR.
MOSS-TTS-Nano
MossTTSNanoModel serializeaza cererile si creeaza stare proaspata CoreML pentru fiecare declaratie. sintetizeaza si sintetizeazaStream pastrare stereo; SpeechGenerationModel returneaza un monodownmix. Pass language: nil. Streams se termina cu un marker final gol.
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)Inference locală nativă
Pentru 12B, markerii de închidere a apelului de instrument adiacenti repetati imediat dupa un apel complet, validat sunt consumati cu acel apel. Ei nu creeaza un alt apel sau isi schimba argumentele. Markerii si argumentele malformate raman erori; E4B retine manipularea stricta.
Pentru a păstra cereri de chat native eșuate, set failureCaptureDirectory în manifestul de inferență. Capture este oprit în mod implicit. Directorul trebuie să refuze grupul și alte acces; noi directoare folosesc 0700 și utilizarea de fișiere de eșec 0600. Fișierele includ organismul de cerere exactă, ID-ul cererii și eroarea. Generațiile de 12B finalizate păstrează, de asemenea, prompte, ID-uri token, producția brută și motiv de oprire înainte de parsare. E4B eșecurile nu au înregistrări de generație brută. Aceste fișiere locale conțin conținut conversație și instrumente; erorile HTTP și cererile de succes nu le includ.
Gemma4UnifiedChat.generateResponse acceptă un apel opțional onGeneration cu un Gemma4GenerationTrace. Apelul se execută înainte de parcurgerea uneltelor, inclusiv atunci când această finalizare este respinsă. Înregistrarea nu modifică prelevarea de probe sau sintaxa de instrumente acceptată.
Agent opţionalDirector încarcă o etapă Gemma 4 12B Unificat INT4 model text alături E4B. Utilizați modelul: "gemma4-12b-it" pentru chat cu instrumente native, istorie rezultat, și anularea. E4B rămâne disponibilă pentru alte cereri. Gemma4UnifiedChat Foloseşte dispozitivul fixat MLX Implementarea textului și lansează fiecare cerere . Răspunsul depărtează delimitatorii canalului de text returnat. Terminatorii de instrumente orfane sunt respinse ca răspunsuri malformate. Gemma 4 12B, conținutul de mesaje de instrumente păstrează JSON ca text, potrivind șablonul de chat punct de control.
Sursa construiește acceptă --inference-manifest modele.json pentru a încărca înscenat Gemma 4 E4B INT4, VoxCPM2, plin Whisper Large-v3, și Silero VAD. Acest serviciu începe offline și respinge identitățile de model incompatibile.
VoxCPM2 suportă greutateaPrecizie: .float16 pentru un punct de control nequantizat. Pentru acest mod, parametrii principali utilizați FP16; AudioVAE și unele calcule rămân FP32. INT8 punctele de verificare sunt respinse.
Gemma4ConversationMessage, Gemma4Tool şi genereazăResponsa păstrează declaraţii de instrumente native, argumente şi istoric de rezultat.InferenceCancellationToken anulează generaţia cooperativ. Silero makeIndependentStramul izolatelor de stare, şi procesChunkTrowing propagează erori de inferenţă.
Whisper ASRModel.de la Decodorul 32-straturi poate construi prefixul său complet Large-v3 CoreML fără textDecodorContextPrefill.mlmodelc și eliberează temporarii pe trepte.Modelul implicit Whisper Modelul CLI rămâne Turbo.
Serviciul nativ serializează chat-ul, sinteza și transcrierea; ușor VAD are o stare de flux separată. Gemma prefill folosește 256-token loturi, cerere KV stat este eliberat, iar cache-ul MLX este limitat la 128 MiB cu o limită de 20 GiB allocator.
Discursul cu flux: adevărat întoarce NDJSON evenimente audio la 48 kHz, urmat de făcut sau eroare. Anulați prin cerereID la /v1/inferință/cancel. GET /v1/inference/capabilități și /v1
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, contentProprietate opțională a modelului
SpeechRuntime oferă încărcare comună, admitere FIFO canelabilă și execuție serializată. Qwen3ASRRuntime selectează MLX sau complet CoreML prin descriptori expliciti; API-urile native și steagurile CLI rămân disponibile.
Serverul audio deţine un permis de execuţie prin fiecare cerere completă, inclusiv fluxul de ieşire, cu până la 32 de cereri de aşteptare. ASR eşecuri produc erori HTTP sau WebSocket evenimente de eroare; transcrieri goale valabile rămân de succes. Playback păstrează mostrele de supraîncărcare. Modele cache şi audio în curs de redare ocupa încă memorie.
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 ruleaza local pe Apple Silicon prin modulul VotralTTSS: 20 voci prestabilite, nouă limbi, și 24 kHz mono speech.
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)
}Doar vocile presetate; clonarea de referință-audio nu este disponibilă. Fluxuri de ieșire audio prin SDK, CLI, WebSocket și HTTP PCM. Furnizarea textului complet de intrare în față. Greutate model de utilizare CC BY-NC 4.0.
import AudioCommon
let player = StreamingAudioPlayer()
player.skipInitialSilence = false