תרגום מכונה · המקור באנגלית
API ופרוטוקולים
ה-Apple Runtime- PocketTTS
PocketTTS מוסיף Native CoreML סינתזה דיבור ל speech-swift.כל השלבים חדלו מביצוע CPU בלבד.The English Alba presets 1,920-sample מסגרות ב 24 kHz (80 ms למסגרת).
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)
}המודול AudioCommon מגדיר פרוטוקולים אבחון מודלים וסוגים משותפים.כל מודל תואם ניתן להשתמש באופן בין-משתנה באמצעות ממשקים אלה.
פרוטוקול Review
┌─────────────────────────────────────────────────────────┐
│ AudioCommon │
│ │
│ AudioChunk SpeechGenerationModel (TTS) │
│ AlignedWord SpeechRecognitionModel (STT) │
│ SpeechSegment ForcedAlignmentModel │
│ SpeechToSpeechModel │
│ VoiceActivityDetectionModel (VAD) │
│ TurnCompletionProvider (EOT) │
│ SpeakerEmbeddingModel │
│ SpeakerDiarizationModel │
│ SpeakerExtractionCapable │
└─────────────────────────────────────────────────────────┘הכרה מחדש
פרוטוקול למודלי דיבור-טקסט.
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
}
סוגים: Qwen3ASRModel, WhisperASRModel, ParakeetASRModel, ParakeetStreamingASRModel, OmnilingualASRModel (CoreML), OmnilingualASRMLXModel (MLX)
המונחים:Model
פרוטוקול למודלי טקסט-to-speech.
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() יש יישום ברירת מחדל כי עוטפת generate() כחלק אחד.מודלים עם זרימה אמיתית (למשל Qwen3-TTS) מעל זה.
סוגים: Qwen3TTSModel, CosyVoiceTTSModel, VoxCPM2TTSModel, KokoroTTSModel, IndexTTS2TTSModel
IndexTTS2TTSModel הוספת התייחסות-audio generate טעינה לקול אפס-צילום משובש ומשתמש IndexTTS2SynthesisOptions לדבר ולעצור את השליטה.
Qwen3-TTS טעינה מקומית
fromLocal אימות קבצים והשלמת shard shard, ואז שואב את גודל המודל, הקוונטים והאדריכלות מן config.jsonאופציונלי configuration הערך הוא רק נפילה עבור metadata מוזנח; מום או סותר metadata לזרוק Qwen3TTSLoadingError.invalidConfiguration לפני הקצאת המודל.
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 מחדלים .noneלכן, זה לא משנה את כל תהליך Metal רצף של יונקים. fromPretrained מחזיק את .pin(fraction: 0.9) כברירת מחדל, שני העומסים המאומנים מקבלים בנפרד tokenizerCacheDir.
הכחדת המונדיאל
פרוטוקול להתאמה ברמה המילה.
public protocol ForcedAlignmentModel: AnyObject {
func align(audio: [Float], text: String, sampleRate: Int, language: String?) -> [AlignedWord]
}
מילים לToSpeechModel
פרוטוקול למודלי דיאלוג של דיבור-אל-סכי.
public protocol SpeechToSpeechModel: AnyObject {
var sampleRate: Int { get }
func respond(userAudio: [Float]) -> [Float]
func respondStream(userAudio: [Float]) -> AsyncThrowingStream<AudioChunk, Error>
}
Conforming Types: PersonaPlexModel
Hibiki TranslationeModel
נהג תרגום דיבור-אל-שטיפה אינו תואם SpeechToSpeechModel מכיוון שהחוזה שונה: קלט ופלט הם שפות שונות, אורך הפלט משתנה (EOS-oriented, כפי שנקבע, מכוסה ב-EOS. max(tSrc * 5/2, tSrc + 20) שלבים - 1.0-1.6x משך מקור על קליפים בסגנון FLEURS), ויש זרם טקסט מקביל לצד אודיו. מדריך מלא
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
}
שיעור הדגימה: 24 kHz מונו, הן קלט והן פלט. שיעור המסגרת: 12.5 Hz (Mimi Codec). שפות מקור: תגית: ES / PT / DE שפה ממוקדת: תגית: Set HIBIKI_GREEDY=1 ההשקה של תהליך עבור מתורגמן מקסימלי decoding (תואמים את יכולות CI).
המונחים: soundivityDetection
פרוטוקול לגילוי פעילות קולית
public protocol VoiceActivityDetectionModel: AnyObject {
var inputSampleRate: Int { get }
func detectSpeech(audio: [Float], sampleRate: Int) -> [SpeechSegment]
}
TurnCompletionProvider
פרוטוקול למתווכים סוף-החזרה ייעץ על ידי StreamingVADProcessor על כל אישור VAD הפסקה. VAD רק שומע שקט; מודל של היפוך מקשיב לפרוזודי של כל הדיבורים, כך שהפסקת אמצע ההסכמה מונעת מהסוכן לחכות בעוד משפט גמור מקבל תשובה מיידית. speech-core" sc_turn_completion_vtable_tראה את 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
}
סוגים: SmartTurnModel (Smart Turn v3.2)
שם המחבר: EmbeddModel
פרוטוקול לדובר הטמיע את החילוץ.
public protocol SpeakerEmbeddingModel: AnyObject {
var inputSampleRate: Int { get }
var embeddingDimension: Int { get }
func embed(audio: [Float], sampleRate: Int) -> [Float]
}
Conforming Types: WeSpeakerModel
תגית:DiarizationModel
פרוטוקול עבור דגמי diarization הדוברים להקצות תוויות דובר למגזרי אודיו.
public protocol SpeakerDiarizationModel: AnyObject {
var inputSampleRate: Int { get }
func diarize(audio: [Float], sampleRate: Int) -> [DiarizedSegment]
}
Conforming סוגים: DiarizationPipeline (Pyannote), SortformerDiarizer
תגית:ExtractionCapable
פרוטוקול דידריזציה מורחב למנועי התומכים במיצוי פלחטים של דובר מטרה תוך שימוש בהפניה המטביעה.לא כל המנועים תומכים בכך (Sortformer הוא מקצה לקצה ואינו מייצר רמקולים המטביעים).
public protocol SpeakerExtractionCapable: SpeakerDiarizationModel {
func extractSpeaker(audio: [Float], sampleRate: Int, targetEmbedding: [Float]) -> [SpeechSegment]
}
Conforming Types: DiarizationPipeline (Pyannote בלבד)
סוגים משותפים
AudioChunk
public struct AudioChunk {
public let samples: [Float] // PCM samples
public let sampleRate: Int // Sample rate (e.g. 24000)
}
דיבור
public struct SpeechSegment {
public let startTime: Float // Start time in seconds
public let endTime: Float // End time in seconds
}
מילים מזוינות
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
}
המונחים:
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)
}
הדיאלוג
קטע מתואם של טקסט דיאלוג רב-רמקולים עם רמקולים אופציונליים ותגי רגש. DialogueParser ו DialogueSynthesizer עבור 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
}
הדיאלוגים
Parses multi-רמקולים טקסט עם תגים inline ([S1]) ותגיות רגש ((happy)).
public enum DialogueParser {
static func parse(_ text: String) -> [DialogueSegment]
static func emotionToInstruction(_ emotion: String) -> String
}
רגשות בנויים: happy/excited, sad, angry, whispers/whispering, laughs/laughing, calm, surprised, seriousתגים לא ידועים עוברים כהנחיות חינם.
הדיאלוג בגודל
Orchestrates multi-segment דיאלוג סינתזה עם קול קול קול קול רמקול, פערי שתיקה וצלב.
public enum DialogueSynthesizer {
static func synthesize(
segments: [DialogueSegment],
speakerEmbeddings: [String: [Float]],
model: CosyVoiceTTSModel,
language: String,
config: DialogueSynthesisConfig,
verbose: Bool
) -> [Float]
}
הדיאלוגים של הסינתזה
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
}
צנרתLLM
פרוטוקול לשילוב מודל שפה עם צינורות קוליים.גשרים LLM ל- VoicePipeline's ASR LLM TTS
public protocol PipelineLLM: AnyObject {
func chat(messages: [(role: MessageRole, content: String)],
onToken: @escaping (String, Bool) -> Void)
func cancel()
}
Built-in Fiter: Qwen3PipelineLLM גשרים Qwen35MLXChat לפרוטוקול זה עם ניקוי אסימונים, ביטול והפסקת ביטוי הצטברות.
אודיו
מנהל אודיו אמין I/O המסלק את AVAudioEngine רותחת. Handles mic ללכוד, resampling, Playback, ורמת אודיו ממטר.
let audio = AudioIO()
try audio.startMicrophone(targetSampleRate: 16000) { samples in
pipeline.pushAudio(samples)
}
audio.player.scheduleChunk(ttsOutput)
audio.stopMicrophone()
AudioIO כולל StreamingAudioPlayer עבור TTS פלט ו AudioRingBuffer עבור העברת אודיו בטוחה חוט בין לכידת לבין חוטי הקצאות.
SystemAudioTap
לוכד את תפוקת המערכת לערבב - מה Mac הוא משחק כיום - כמו מונו Float32 בתעריף מדגם caller-chosen (תהליך אודיו, macOS 14.4+ המונו הגלובלי אינו כולל את התהליך הנוכחי כברירת מחדל, כך משחק עצמאי של אפליקציה לעולם אינו מכווץ, ורק מכשיר פרטי.
let tap = SystemAudioTap()
try tap.start(targetSampleRate: 16000) { samples in
pipeline.pushAudio(samples)
}
tap.stop()
לכידת שורדת מתגי ברירת מחדל - המפיץ הפנימי נבנה מחדש כאשר שערי התערובת משתנים.ה אפליקציה המארחת חייבת להכריז NSAudioCaptureUsageDescriptionאם אישור ההקלטה נשלל, עדיין ניתן ליצור ברז רק שתיקה: צפו framesCaptured גדל בזמן nonSilentFrames נשאר 0 ונכשל סגור.
מקורות עצמאיים
שתי כיתות ללכוד לחשוף את הפעמיםtamped overloads. CapturedAudioChunk נושאת מונו samples ב sampleRate בתוספת אופציונלי מאך hostTime
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)
}
עבור לכידת בלבד, enableAEC: true מפעילה עיבוד קול אפל לפני פורמט המיקרופון קורא, ו enablePlayback: false omits את השחקן הבלתי מנוצל. על macOS, השמעת המכשיר הוסרה מדגימות מיקרופון וכישלון כדי להתחיל AEC מדווח במקום ליפול בחזרה לקלט.
רישום קבצים והכרה
CapturedAudioChunk יש גם מונוטוני frameIndex ו isFinal סימן. AudioFileLoader.stream מייצרת אלה על הביקוש עם זיכרון כבול, מחזרים פעם עם מצב ממיר מתמשך, וממוצע כל ערוצי קלט כברירת מחדל. .first או או .select([indices]) כאשר ערוץ מתפרץ ידוע.
StreamingRecognitionModel ו StreamingRecognitionSession לספק תוספת של cache-preworth API על פני Parakeet סטרימינג ו 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)
}
עונש מאסר:PieceModel
קורא משותף של Protobuf עבור LawPiece .model קבצים, חיים AudioCommon.כל מודול שצריך לפענח חתיכות משפט (PersonaPlex, OmnilingualASR, ASR / TTS) בונה קוד משלו על גבי קורא יחיד זה במקום להפעיל מחדש את תבנית חוט 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
}
Used על ידי: OmnilingualASR.OmnilingualVocabulary, PersonaPlex.SentencePieceDecoder, מכוסה על ידי 7 בדיקות יחידות ב Tests/AudioCommonTests/SentencePieceModelTests
MLXCommon.SDPA
עוזרי תשומת לב של Dot-product שיתפו את כל MLX מודול תשומת לב (Qwen3-ASR / Qwen3-TTS / Qwen3-Chat / CosyVoice / PersonaPlex / OmnilingualASR) כל מודול שומר על התחזיות שלו - SDPA מטפל רק בתשומת הלב של ה-reshape.
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
}
כל ה-reshape קורא להשתמש -1 עבור ממד אצווה כך שהתומכים מרכיבים עם MLX.compile(shapeless:) גרפים שונים אצווה בזמן ריצה (למשל Qwen3-TTS Talker autoregressive decode).
HTTP API Server
The The The speech-server בינארי חושף כל מודל ב- speech-swift כ-HTTP REST נקודות קצה בתוספת נקודת קצה WebSocket אשר מיישמת את נקודות הקצה ZXQXQ2XZ המיישם את נקודות הקצה של ZXQ OpenAI בזמן אמת APIמודלים עמוסים בכבדות על פי בקשה ראשונה; עוברים --preload לחמם את כולם בסטארט-אפ.
swift build -c release
.build/release/speech-server --port 8080
# Preload every model at startup
.build/release/speech-server --port 8080 --preload
REST Endpoints
| סוף סוף | שיטות | בקשה לבקשה | תגובה |
|---|---|---|---|
/transcribe | POST | audio/wav גוף | ג'ייסון { text } (Qwen3-ASR) |
/v1/audio/transcriptions | POST | Multipart { file, model, response_format?, language?, prompt?, temperature? } | ג'ייסון, טקסט, פועלוז ג'סון, SRT, או VTT |
/v1/audio/speech | POST | JSON { model, input, voice, response_format?, speed? } | audio/wav או audio/pcm גוף |
/speak | POST | JSON { text, engine?, language?, voice? } | audio/wav Qwen3-TTS, CosyVoice Kokoro |
/respond | POST | audio/wav גוף | audio/wav גוף PersonaPlex |
/enhance | POST | audio/wav גוף | audio/wav גוף DeepFilterNet3 |
/vad | POST | audio/wav גוף | רשימת JSON |
/diarize | POST | audio/wav גוף | JSON DiarizedSegment רשימה |
/embed-speaker | POST | audio/wav גוף | JSON [Float] (256-dim) |
/v1/audio/speech מפות תקן TTS מודל ושמות קול למנועי מקומיים.זה ברירת מחדל ל- WAV; response_format: "pcm" מחזירה 24 kHz מונו PCM16 אודיו קטן-endian. Unsupported דחוס פורמטים נדחו במפורש.
/v1/audio/transcriptions מקבל קובץ WAV כ- Multipart טופס נתונים ברירת מחדל עבור {"text":"..."}; response_format ניתן גם לבקש טקסט, פועלו של ג'ייסון, SRT או VTT על Apple, מודלים לבחור מקומי ASR מנוע speech-core שרת On Linux ו Windows שימוש ב- lazily טעון Parakeetקבלה PCM16/24/32 או או Float32 WAV« וכובעים עולים 25 MiB ו 10 דקות.
# 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 Endpoint ws://host:port/v1/realtime יישום פרוטוקול OpenAI בזמן אמת.כל ההודעות הן JSON עם ADD type discriminator; מטענים אודיו הם base64-encoded PCM16 ב 24 kHz מונו.
במהלך עומסי מודל קרים או דורות ארוכים, השרת פולט קל realtime.keepalive אירועי JSON ו websocket pong Control מסגרות בערך כל 15 שניות עד שהמודל מוכן.
אירועים Server
| אירוע אירועים | מטרה |
|---|---|
session.update | מנוע תצוגה, שפה, קול, פורמט אודיו, שרת אופציונלי VAD |
input_audio_buffer.append | נספח base64 PCM16 נתח קלט |
input_audio_buffer.commit | התחייבו את אודיו המבוקש לתעתיק |
input_audio_buffer.clear | עקבו אחרי buffer |
response.create | בקש TTS סינתזה עבור הטקסט/ההורות המסופקים |
תגית: Customer Events
| אירוע אירועים | משמעות משמעות משמעות |
|---|---|
session.created | Handshake Complete, ברירת מחדל config פולטת |
session.updated | לאחרונה session.update הודה |
input_audio_buffer.speech_started | שרת VAD זיהתה את תחילתו של תור דיבור |
input_audio_buffer.speech_stopped | שרת VAD סיימה את התור לאחר שתיקה או הגבלת משך הזמן |
input_audio_buffer.committed | אודיו התקבל ומסור לתעתיק |
conversation.item.input_audio_transcription.completed | ASR תוצאה עם טקסט תמליל סופי |
response.audio.delta | Base64 PCM16 נתח אודיו מסונתז |
response.audio.done | לא עוד קטעי אודיו עבור תגובה זו |
response.done | תגובה סופית (metadata + latency stats) |
error | מעטפה שגיאה עם type ו message |
הפעלות אוטומטית עם השרת VAD
Explicit input_audio_buffer.commit נשאר ברירת מחדל.כדי לאפשר לשרת קרוב ו transcribe פונה באופן אוטומטי עם Silero VAD. turn_detectionהשרת שומר רק את ה-Pre-roll המבוקש בזמן idle, פולט את הדיבור מתחיל/עצור ומבצע אירועים על מנת, ומסגור את התפנית הפעילה ברציפות. 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
השרת חי ב AudioServer SPM מוצר.לדוגמה, לקוח דפדפן נשלח בכתובת Examples/websocket-client.html - פתח אותו לצד שרת ריצה כדי להניע את מלוא ASR + TTS.
דגם Downloads
כל הדגמים הורדים מ- HuggingFace לשימוש ראשון וצפופים לשימוש ראשון. ~/Library/Caches/qwen3-speech/The AudioCommon מודול מספק משותף HuggingFaceDownloader זה מטפל הורדות, צ'נג, ואימות יושרה.
הורד Retries
עומסי מודל והורדת APIs מקבלים downloadOptions: DownloadOptions. onRetry מקבל מקבל DownloadRetryEvent לפני כל גב, עם modelId, attempt, maxAttemptsהמקור errorו retryInSeconds. nil פירוש הדבר שלא תישארו חזרות; נפילה מלאה של פחמימות עדיין יכולה להצליח.
Callbacks הם סינכרוניים @Sendableללא התחייבות מרכזית לביטול לא מייצרת אירוע משוחרר. retryDelaysSeconds כדי להתאים אישית את המדיניות ([] משמעות הדבר היא ניסיון אחד); הטיעון ההורדה המפורשת API לוקח precedence. Default ניסיון ספירות והתקדמות חד פעמית נשמרים. AudioLog.download.
ביטול משותף
עבור MLX Qwen3ASRModel.transcribeCheckingCancellation(audio:sampleRate:options:) זורקים CancellationError כאשר ביטול המשימה נצפתה לפני ביצוע תכונה, סיבולת, קודר prefill, או צעד decoding. GPU עבודה כבר הוגש יכול לסיים. transcribe ו transcribeBatch ה- API שומר על התנהגותם הלא-מחדשת ומתעלם מביטול המשימות. speech-server השתמש בנקודת הכניסה של ביטול-מודע עבור Qwen3-ASR.
MOSS-TTS-Nano
MossTTSNanoModelize בקשות ויוצר מצב CoreML עבור כל מילה.סינתנת סטream לשמר סטריאו; נאונדרציהModel מחזיר מונו למטה.com שפה: nil. Streams לסיים עם סימן סופי ריק.
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)אינדיאנית מקומית
עבור 12B, שוב ושוב סמנים סגורים כלי מיד לאחר שיחה מלאה, מאומתת נצרכת עם שיחת טלפון זו.הם לא יוצרים שיחה אחרת או משנים את טענותיה. Standalone סמנים וטיעונים ממותקים נשארים שגיאות; E4B
כדי לשמור על בקשות צ'אט מקומיות כושלות, להגדיר failureCaptureDirectory בהקצאת ההקצאה, לכידת היא כברירת מחדל.המנהל חייב להכחיש קבוצה וגישה אחרת; מנהלים חדשים משתמשים בדירקטוריון חדש משתמשים 0700 קבצי כישלון משתמשים 0600קבצים כוללים את גוף הבקשה המדויק, בקשה מזהה וטעייה.שלמו 12B דורות גם לשמור על הזיהוי, זיהויים אסימונים, פלטי גלם ופתרון לפני הפיצות. E4B כשלים אינם מכילים תיעוד של דור גולמי.קבצים מקומיים אלה מכילים שיחות ותכנים בכליים; שגיאות HTTP ובקשות מוצלחות אינם כוללים אותם.
Gemma4UnifiedChat.generateResponse מקבל אופציונלי onGeneration callback with a Gemma4GenerationTrace
הסוכן האופטימי טוען כי הוא מוקרן Gemma 4 12B Unified INT4 מודל טקסט לצד E4Bהשתמש במודל: "gemma4-12b-itעבור צ'אט עם כלים מקומיים, תוצאות היסטוריה וביטול. E4B נשאר זמין עבור בקשות אחרות. Gemma4UnifiedChat שימוש ב- pinned MLX יישום טקסט ומשחרר כל בקשתו של KV cache. Response parsing מסירים ערוץ דלימירים מטקסט חוזר.המכונים נדריני כלי נדחו כתגובה מופרכת. Gemma 4 12B, תוכן mes-mesage משמר את JSON כטקסט, תואם את תבנית הצ'אט של המחסומים.
מקור: Get Get --inference-manifest מודלים: ג'ייסון לטעון Gemma 4 E4B INT4, VoxCPM2מלא, Whisper Large-v3ו Silero VADשירות זה מתחיל במצב לא מקוון ודוחק זהויות מודל לא מקבילות.
VoxCPM2 תומך בהכנת משקל: .float16 למחסום לא צפוי.הפרמטרים העיקריים שלו משתמשים ב- FP16; AudioVAE וכמה חישובים נותרו FP32. INT8 נדחו עבור מצב זה.
Gemma4ConversationMessage, Gemma4Tool ו-Response לשמר הצהרות כלי יליד, טיעונים ותוצאה היסטוריה. InferenceCancellationToken מבטל את הדור בשיתוף פעולה. Silero MakeIndependentStream מבודדת המדינה, ומעבדת צ'אנק-Throwing propagates inference שגיאות.
Whisper ASRModel. fromDirectory העומסים עלו במלואו Large-v3 CoreML תגית: The 32-layer decoder יכול לבנות את הקידומת שלו ללא טקסט-DecoderContextPrefill.ml Modelc ושחרר את קצבי ההקצבה לצעד. Whisper מודל CLI נשאר טורבו.
שירות Native סידורי צ'אט, סינתזה, ותעתיק; קל משקל VAD יש מצב זר נפרד. Gemma שימושים prefill 256- אצילות, בקשת KV המדינה שוחררה, ואת המדינה MLX שפם קפץ 128 MiB עם 20 GB allocator Limit.
נאומו עם זרם: החזרה האמיתית של NDJSON Audio Events 48 kHz, לאחר מכן נעשה או שגיאה.תבטל על ידי בקשה ל- /v1/ Inference / Cancel. GETv1/ סודיות / נקודות ו /v1/ הערכה / שיפור דוח מודל זהויות, פרמטר dtypes, סיכות נכסים ודלפק זיכרון.
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, contentבעלות מודל אופטי
דיבור Runtime מספק טעינה משותפת, כניסה ל-FIFO והוצאה להורג סדרתית Qwen3ASRRuntime בוחרת MLX או מלא CoreML באמצעות פקודות מפורשות; ממשקי API ו- CLI נותרו זמינים.
שרת השמע מחזיקה ברישיון הוצאה להורג אחד בכל בקשה מלאה, כולל פלט מפלט, עם עד 32 בקשות לחכות. ASR כישלונות לייצר שגיאות HTTP או WebSocket אירועים; תמלילים ריקים נותרו מוצלחים.
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 פועל באופן מקומי על Apple Silicon דרך מודול Voxtral TTS: 20 קולות מוקדמים, תשע שפות ו-24 דיבור מונו הרץ.
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)
}קולות מוקדמים בלבד; שיבוט הפניה-קולי אינו זמין. SDKCLI, WebSocket ו-HTTP PCM. לספק את הטקסט המלא של קלט למעלה הקדמי.מודל משקולות להשתמש CC BY-NC 4.0.
import AudioCommon
let player = StreamingAudioPlayer()
player.skipInitialSilence = false