תרגום מכונה · המקור באנגלית

API ופרוטוקולים

ה-Apple Runtime- PocketTTS

PocketTTS מוסיף Native CoreML סינתזה דיבור ל speech-swift.כל השלבים חדלו מביצוע CPU בלבד.The English Alba presets 1,920-sample מסגרות ב 24 kHz (80 ms למסגרת).

Swift ו- CLI Use

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

סוף סוףשיטותבקשה לבקשהתגובה
/transcribePOSTaudio/wav גוףג'ייסון { text } (Qwen3-ASR)
/v1/audio/transcriptionsPOSTMultipart { file, model, response_format?, language?, prompt?, temperature? }ג'ייסון, טקסט, פועלוז ג'סון, SRT, או VTT
/v1/audio/speechPOSTJSON { model, input, voice, response_format?, speed? }audio/wav או audio/pcm גוף
/speakPOSTJSON { text, engine?, language?, voice? }audio/wav Qwen3-TTS, CosyVoice Kokoro
/respondPOSTaudio/wav גוףaudio/wav גוף PersonaPlex
/enhancePOSTaudio/wav גוףaudio/wav גוף DeepFilterNet3
/vadPOSTaudio/wav גוףרשימת JSON
/diarizePOSTaudio/wav גוףJSON DiarizedSegment רשימה
/embed-speakerPOSTaudio/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.createdHandshake 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.completedASR תוצאה עם טקסט תמליל סופי
response.audio.deltaBase64 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)

MOSS-TTS-Nano

אינדיאנית מקומית

עבור 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 8080
POST /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

קרא את Voxtral TTSמדריך