Speech Server

Apple için yerel çalışma zamanı · PocketTTS

PocketTTS, speech-swift'e yerel CoreML konuşma sentezi ekler. Tüm aşamalar varsayılan olarak yalnızca CPU kullanır. İngilizce Alba sesi 24 kHz'de 1.920 örneklik kareler, yani kare başına 80 ms ses aktarır.

Swift ve CLI kullanımı

pocket-tts ve alba sesini kullanın. HTTP akışı PCM gerektirir; WebSocket ses parçaları gönderir. Mevcut varsayılan modeller korunur.

curl http://localhost:8080/v1/audio/speech \
  -H 'Content-Type: application/json' \
  -d '{"model":"pocket-tts","voice":"alba","input":"Hello world.","response_format":"pcm","stream":true}' \
  -o hello.pcm

speech-server; her Soniqo modelini basit bir REST API ile sunan ve /v1/realtime adresinde OpenAI Realtime API uyumlu bir WebSocket sağlayan yerel bir HTTP + WebSocket sunucusudur. speech CLI ile aynı Homebrew bottle içinde gelir — brew install speech her ikisini birden PATH'inize yerleştirir.

Kurulum ve çalıştırma

brew install speech

speech-server --port 8080
# Starting server on http://127.0.0.1:8080
# Endpoints:
#   POST /transcribe  - Speech-to-text (WAV body or JSON with audio_base64)
#   POST /speak       - Text-to-speech (JSON: {text, engine?, language?})
#   POST /respond     - Speech-to-speech (WAV body, voice/max_steps via query)
#   POST /enhance     - Speech enhancement (WAV body)
#   GET  /health      - Health check
#   WS   /v1/realtime - OpenAI Realtime API (JSON events, base64 PCM16 audio)

Komut satırı bayrakları

BayrakVarsayılanAçıklama
--host127.0.0.1Bind adresi. LAN'a açmak için 0.0.0.0 olarak değiştirin.
--port8080TCP portu.
--preloadkapalıTüm modelleri başlangıçta hevesle yükler. Boot daha yavaş (~30–60 sn) ama ilk istek gecikmesi sıfırdır.

Modeller ilk kullanımda indirilir ve ~/Library/Caches/qwen3-speech/ içinde önbelleğe alınır. Belirli bir model için ilk istek, indirme + yükleme maliyetini öder (model boyutuna göre 30 sn – 2 dk); sonraki istekler sıcaktır.

REST uç noktaları

POST /transcribe — Konuşmadan metne

Ya ham bir WAV gövdesi ya da base64 ile kodlanmış ses içeren bir JSON zarfı kabul eder.

# WAV body (preferred — lower overhead)
curl -X POST http://127.0.0.1:8080/transcribe \
  -H "Content-Type: audio/wav" \
  --data-binary @recording.wav

# JSON with base64
curl -X POST http://127.0.0.1:8080/transcribe \
  -H "Content-Type: application/json" \
  -d '{"audio_base64":"'"$(base64 -i recording.wav)"'","language":"en"}'

Yanıt: {"text": "…", "language": "en", "confidence": 0.93}.

POST /speak — Metinden konuşmaya

curl -X POST http://127.0.0.1:8080/speak \
  -H "Content-Type: application/json" \
  -d '{"text":"Hello, world!","engine":"kokoro","language":"en"}' \
  --output hello.wav

Yanıt gövdesi bir WAV blob'udur. Desteklenen engine değerleri: qwen3 (varsayılan), cosyvoice, kokoro.

POST /respond — Konuşmadan konuşmaya

curl -X POST "http://127.0.0.1:8080/respond?voice=en_female_calm&max_steps=256" \
  -H "Content-Type: audio/wav" \
  --data-binary @question.wav \
  --output answer.wav

PersonaPlex 7B'yi çalıştırır — ses girer, ses çıkar. Transkript bir X-Response-Text başlığında döndürülür. Ses preset adları için PersonaPlex kılavuzuna bakın.

POST /enhance — Konuşma iyileştirme

curl -X POST http://127.0.0.1:8080/enhance \
  -H "Content-Type: audio/wav" \
  --data-binary @noisy.wav \
  --output clean.wav

48 kHz'de DeepFilterNet3. Giriş gerekirse yeniden örneklenir.

GET /health — Canlılık denetimi

curl http://127.0.0.1:8080/health
# {"status":"ok"}

HTTP ve gerçek zamanlı WebSocket testleri, 10 saniyelik başlatma süresi içinde /health uç noktasının HTTP 200 ve {"status":"ok"} döndürmesini bekler. Başlatma hataları test hazırlığını başarısız kılar; temizleme işlemi, bağlantı noktasını yeniden kullanmadan önce sunucunun kapanmasını bekler.

WebSocket: /v1/realtime

OpenAI Realtime API ile doğrudan uyumlu — aynı JSON olay şeması (session.update, input_audio_buffer.append, response.create, response.audio.delta, …) ile 24 kHz'de base64 kodlu PCM16 ses. OpenAI'ın Realtime SDK'sına karşı yazılmış istemciler, kod değişikliği olmadan (yalnızca WebSocket URL'sini değiştirerek) speech-server ile çalışır.

Sunucu VAD ile otomatik konuşma sıraları

Elle input_audio_buffer.commit varsayılan olarak kalır. Sunucuda Silero VAD çalıştırmak ve tamamlanan her konuşma sırasını otomatik yazıya dökmek için session.turn_detection değerini server_vad yapın. Ön ek ses korunur, boşta arabelleğe alma sınırlıdır, speech_started, speech_stopped ve committed olayları sırayla gönderilir ve sürekli etkin bir sıra yapılandırılan üst sınırda zorla kapatılır.

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
    }
  }
}));

JavaScript örneği

const ws = new WebSocket("ws://127.0.0.1:8080/v1/realtime");

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: "session.update",
    session: { modalities: ["audio", "text"] }
  }));

  // Stream PCM16 mono 24kHz audio from the mic:
  const audioBase64 = await capturePCM16Chunk();
  ws.send(JSON.stringify({
    type: "input_audio_buffer.append",
    audio: audioBase64
  }));

  ws.send(JSON.stringify({ type: "response.create" }));
};

ws.onmessage = ev => {
  const msg = JSON.parse(ev.data);
  if (msg.type === "response.audio.delta") {
    playPCM16Base64(msg.delta);
  }
};

Python örneği

import asyncio, base64, json, wave, websockets

async def main():
    async with websockets.connect("ws://127.0.0.1:8080/v1/realtime") as ws:
        await ws.send(json.dumps({"type": "session.update",
                                  "session": {"modalities": ["audio", "text"]}}))

        with wave.open("question.wav", "rb") as wav:
            pcm16 = wav.readframes(wav.getnframes())
        await ws.send(json.dumps({"type": "input_audio_buffer.append",
                                  "audio": base64.b64encode(pcm16).decode()}))
        await ws.send(json.dumps({"type": "response.create"}))

        async for raw in ws:
            msg = json.loads(raw)
            if msg["type"] == "response.audio.delta":
                open("answer.pcm", "ab").write(base64.b64decode(msg["delta"]))
            elif msg["type"] == "response.done":
                break

asyncio.run(main())

Dağıtım notları

Kaynak kodu

İsteğe bağlı model yönetimi

SpeechRuntime paylaşılan yükleme, iptal edilebilir FIFO kabulü ve sıralı çalıştırma sağlar. Qwen3ASRRuntime açık tanımlayıcılarla MLX veya tam CoreML seçer; yerel API ve CLI seçenekleri kullanılabilir kalır.

Sunucu akış çıktısı dahil tüm istek boyunca bir çalıştırma izni tutar ve en fazla 32 isteği bekletir. ASR hataları HTTP hataları veya WebSocket hata olayları olarak iletilir; geçerli boş dökümler başarılı kalır. Oynatma, taşan örnekleri korur. Önbellekteki modeller ve bekleyen ses verisi bellekte yer kaplamaya devam eder.

Voxtral TTS

Voxtral 4B TTS 2603, VoxtralTTS modülüyle Apple Silicon üzerinde yerel çalışır: 20 hazır ses, dokuz dil ve 24 kHz tek kanallı konuşma.

curl http://localhost:8080/v1/audio/speech \
  -H 'Content-Type: application/json' \
  -d '{"model":"voxtral-tts","input":"Hello, welcome to Voxtral.","voice":"casual_male","response_format":"wav"}' \
  -o hello.wav

Yalnızca hazır sesler desteklenir; referans sesten klonlama kullanılamaz. SDK, CLI, WebSocket ve HTTP PCM, üretim sırasında ses iletir. Giriş metni başlangıçta eksiksiz verilmelidir. Model ağırlıkları CC BY-NC 4.0 lisansını kullanır.

Voxtral TTS kılavuzunu okuyun

Gemma 4 12B istem ön doldurma

Yerel çıkarım hizmeti, hazırlanan Gemma 4 12B Unified INT4 modelini agentDirectory üzerinden yükler. Sohbet için gemma4-12b-it model kimliğiyle seçin.

Gemma 4 12B ön doldurma işlemi, KV durumunu en fazla 256 tokenlık gruplarla doldurur ve ilk örnekleme logits değerlerini istemin son tokenından hesaplar. Ara konumlardaki sözcük dağarcığı izdüşümleri atlanır. İstek tamamlandığında veya iptal edildiğinde KV önbelleği serbest bırakılır.

Değişmeyen bir sistem/araç öneki tam 256 tokenlık bloklar halinde tutulabilir. Her istek bağımsız bir çalışma kopyasını genişletir; kullanıcı mesajları, konuşma geçmişi ve araç sonuçları saklanan duruma eklenmez. SPEECH_SWIFT_GEMMA4_PREFIX_CACHE_MB belleği MiB olarak sınırlar; 0 yeniden kullanımı kapatır. clearPrefixCache() saklanan durumu serbest bırakır. HTTP yeniden kullanılan tokenları cachedPrefixTokens ile bildirir; kaynak bilgisi gemma4_12b_prefix_cache_budget_bytes değerini kaydeder. Soğuk istekler istemin tamamını işlemeye devam eder.

Gemma’nın yerel araç geçmişi, araç sonuçlarını ve asistanın devam yanıtını aynı model turunda tutar. Ardından bir kullanıcı mesajı gelirse, sonraki kullanıcı turu açılmadan önce model turu kapatılır. Her sonucu özgün toolCallID değeriyle gönderin.