Speech Server
بيئة تشغيل أصلية لأجهزة Apple · PocketTTS
يضيف PocketTTS توليد الكلام الأصلي عبر CoreML إلى speech-swift. تعمل جميع المراحل على CPU فقط افتراضيًا. يبث صوت Alba الإنجليزي إطارات من 1,920 عينة بتردد 24 kHz، أي 80 مللي ثانية لكل إطار.
استخدم pocket-tts مع الصوت alba. يتطلب بث HTTP تنسيق PCM؛ ويرسل WebSocket أجزاء الصوت. تظل النماذج الافتراضية الحالية كما هي.
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.pcmspeech-server هو خادم محلّي يعمل على HTTP وWebSocket يُتيح كلّ نموذج من نماذج Soniqo عبر واجهة REST بسيطة، إلى جانب WebSocket متوافق مع OpenAI Realtime API على /v1/realtime. يُشحن في الحزمة نفسها من Homebrew مع واجهة speech السطرية — يضع brew install speech كليهما في PATH الخاصّ بك.
التثبيت والتشغيل
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)
وسوم سطر الأوامر
| الوسم | الافتراضي | الوصف |
|---|---|---|
--host | 127.0.0.1 | عنوان الربط. غيّره إلى 0.0.0.0 للإتاحة على الشبكة المحلّية. |
--port | 8080 | منفذ TCP. |
--preload | معطَّل | تحميل جميع النماذج بنشاط عند بدء التشغيل. إقلاع أبطأ (نحو 30–60 ثانية) لكن دون أيّ زمن استجابة لأوّل طلب. |
تُنزَّل النماذج عند أوّل استخدام وتُخزَّن في ~/Library/Caches/qwen3-speech/. يدفع الطلب الأوّل لأيّ نموذج تكلفة التنزيل والتحميل (من 30 ثانية إلى دقيقتين بحسب حجم النموذج)؛ أمّا الطلبات اللاحقة فتكون ساخنة.
نقاط نهاية REST
POST /transcribe — كلام إلى نص
يقبل إمّا متن WAV خامًا أو غلاف JSON يحتوي على صوت مُشفَّر بصيغة base64.
# 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"}'
الاستجابة: {"text": "…", "language": "en", "confidence": 0.93}.
POST /speak — نص إلى كلام
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
متن الاستجابة هو ملفّ WAV. القيم المدعومة لـ engine: qwen3 (الافتراضي)، cosyvoice، kokoro.
POST /respond — كلام إلى كلام
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 — كلام دخل، كلام خرج. يُعاد النصّ المفرَّغ في ترويسة X-Response-Text. راجع دليل PersonaPlex للاطّلاع على أسماء الإعدادات الصوتية المسبقة.
POST /enhance — تحسين الكلام
curl -X POST http://127.0.0.1:8080/enhance \
-H "Content-Type: audio/wav" \
--data-binary @noisy.wav \
--output clean.wav
DeepFilterNet3 عند 48 kHz. تُعاد عيّنة المدخلات إذا لزم الأمر.
GET /health — فحص الجاهزية
curl http://127.0.0.1:8080/health
# {"status":"ok"}
تنتظر اختبارات HTTP وWebSocket في الوقت الفعلي حتى يعيد /health الحالة HTTP 200 مع {"status":"ok"}، بمهلة بدء قدرها 10 ثوانٍ. تؤدي أخطاء بدء التشغيل إلى فشل إعداد الاختبار، وتنتظر مرحلة التنظيف توقف الخادم قبل إعادة استخدام منفذه.
WebSocket: /v1/realtime
متوافق فورًا مع OpenAI Realtime API — مخطّط أحداث JSON نفسه (session.update، input_audio_buffer.append، response.create، response.audio.delta، …) مع صوت PCM16 مُشفَّر بصيغة base64 عند 24 kHz. تعمل العملاء المكتوبون لـ SDK الزمن الفعلي من OpenAI مقابل speech-server دون أيّ تغيير في الشيفرة (يكفي تبديل عنوان WebSocket).
الأدوار التلقائية عبر VAD في الخادم
يبقى input_audio_buffer.commit اليدوي هو الإعداد الافتراضي. اضبط session.turn_detection على server_vad لتشغيل Silero VAD في الخادم ونسخ كل دور كلام مكتمل تلقائياً. يحتفظ الخادم بصوت البادئة، ويحدّ التخزين أثناء الخمول، ويرسل أحداث speech_started وspeech_stopped وcommitted بالترتيب، ويفرض إنهاء الدور عند الحد الأقصى المحدد.
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
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
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())
ملاحظات النشر
- لا توجد مصادقة افتراضيًا. يربط
speech-serverالعنوان127.0.0.1ويثق بكلّ المتّصلين. ضعه خلف وكيل عكسي (Caddy أو nginx أو tailscale) إذا أردت إتاحته خارج localhost. - تُحمَّل النماذج بكسل. الطلب الأوّل لـ
/transcribeيُطلق تنزيل وتحميل ASR (نحو 700 MB، 30–60 ثانية). استخدم--preloadلإلغاء بدء التشغيل البارد على حساب إقلاع أبطأ. - لا يوجد بثّ تفريغ بعد. ينتظر
POST /transcribeملفّ WAV كاملًا مسبقًا. استخدم/v1/realtimeللبثّ. - Systemd / launchd. لا تُشحن وحدة خدمة اليوم. تعمل
nohup speech-server &البسيطة أو مشرف العمليّات المعتاد لديك جيّدًا.
الشيفرة المصدرية
- Sources/AudioServer — موجّه HTTP مبنيّ على Hummingbird، سجلّ نماذج كسول، معالج WebSocket لـ
/v1/realtime. - Sources/AudioServerCLI — نقطة دخول
@main، محلّل المعاملات. - المصدر الأصلي: مرجع OpenAI Realtime API.
إدارة اختيارية للنماذج
يوفر SpeechRuntime تحميلًا مشتركًا وقبولًا قابلًا للإلغاء وفق FIFO وتنفيذًا تسلسليًا. يختار Qwen3ASRRuntime بين MLX وCoreML الكامل بواصفات صريحة، وتظل واجهات API الأصلية وخيارات CLI متاحة.
يحتفظ الخادم بإذن تنفيذ طوال الطلب بما في ذلك الإخراج المتدفق، مع انتظار 32 طلبًا كحد أقصى. تنتج إخفاقات ASR أخطاء HTTP أو أحداث خطأ WebSocket، بينما تظل النصوص الفارغة الصحيحة ناجحة. يحتفظ التشغيل بالعينات الزائدة. تستمر النماذج المخزنة مؤقتًا والصوت المنتظر في شغل الذاكرة.
Voxtral TTS
يعمل Voxtral 4B TTS 2603 محليًا على Apple Silicon عبر وحدة VoxtralTTS: 20 صوتًا مُعدًا مسبقًا وتسع لغات وصوت أحادي بتردد 24 kHz.
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تتوفر أصوات جاهزة فقط؛ لا يتوفر استنساخ الصوت من تسجيل مرجعي. تُخرج SDK وCLI وWebSocket وHTTP PCM الصوت أثناء التوليد. يجب تقديم نص الإدخال كاملاً في البداية. أوزان النموذج مرخصة بموجب CC BY-NC 4.0.
تهيئة موجّه Gemma 4 12B
تُحمِّل خدمة الاستدلال الأصلية نموذج Gemma 4 12B Unified INT4 المُجهَّز باستخدام agentDirectory. اختره للمحادثة بمعرّف النموذج gemma4-12b-it.
تملأ مرحلة التهيئة في Gemma 4 12B حالة KV على دفعات تصل إلى 256 رمزًا، وتحسب قيم logits لأخذ العينة الأولى من الرمز الأخير في الموجّه. تُتجاوز إسقاطات المفردات للمواضع الوسيطة. تُحرَّر ذاكرة KV الخاصة بالطلب بعد اكتماله أو إلغائه.
يمكن الاحتفاظ ببادئة واحدة ثابتة للنظام والأدوات في كتل كاملة من 256 رمزًا. يوسّع كل طلب نسخة عمل مستقلة؛ ولا تُضاف رسائل المستخدم أو سجل المحادثة أو نتائج الأدوات إلى اللقطة المحفوظة. يحدّد SPEECH_SWIFT_GEMMA4_PREFIX_CACHE_MB الذاكرة بوحدة MiB، وتعطّل القيمة 0 إعادة الاستخدام. تحرّر clearPrefixCache() اللقطة. يعرض HTTP الرموز المعاد استخدامها في cachedPrefixTokens، وتسجّل معلومات المصدر gemma4_12b_prefix_cache_budget_bytes. تظل الطلبات الباردة تعالج الموجّه كاملًا.
يحتفظ سجل أدوات Gemma الأصلي بنتائج الأدوات ومتابعة المساعد ضمن دور النموذج نفسه. إذا تلتها رسالة مستخدم، يُغلق دور النموذج قبل فتح دور المستخدم التالي. أرسل كل نتيجة مع قيمة toolCallID الأصلية الخاصة بها.