speech-server
speech-server เป็นเซิร์ฟเวอร์ HTTP + WebSocket แบบ local ที่เปิดโมเดล Soniqo ทุกตัวผ่าน REST API ที่เรียบง่าย พร้อมกับ WebSocket ที่เข้ากันได้กับ OpenAI Realtime API ที่ /v1/realtime มันถูกบรรจุใน Homebrew bottle เดียวกับ CLI speech — คำสั่ง brew install speech จะติดตั้งทั้งสองตัวลงใน PATH ของคุณ
การติดตั้งและการรัน
brew install soniqo/tap/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 | ที่อยู่ bind เปลี่ยนเป็น 0.0.0.0 เพื่อเปิดให้เข้าถึงผ่าน LAN |
--port | 8080 | พอร์ต TCP |
--preload | ปิด | โหลดโมเดลทั้งหมดทันทีตอน startup boot ช้ากว่า (~30–60 วินาที) แต่ request แรกไม่หน่วง |
โมเดลถูกดาวน์โหลดเมื่อใช้ครั้งแรกและ cache ไว้ใน ~/Library/Caches/qwen3-speech/ request แรกสำหรับโมเดลแต่ละตัวต้องเสียค่าใช้จ่ายดาวน์โหลด + โหลด (30 วินาที – 2 นาที ขึ้นอยู่กับขนาดโมเดล) request ถัดไปจะอุ่นแล้ว
REST endpoint
POST /transcribe — เสียงเป็นข้อความ
รับได้ทั้ง WAV body ดิบ หรือ JSON envelope ที่มีเสียงเข้ารหัส 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
Body ของการตอบกลับเป็น WAV blob ค่า 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 — เสียงเข้า เสียงออก Transcript ถูกส่งกลับผ่าน header X-Response-Text ดู คู่มือ PersonaPlex สำหรับชื่อ voice preset
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 อินพุตจะถูก resample หากจำเป็น
GET /health — ตรวจสอบสถานะ
curl http://127.0.0.1:8080/health
# {"status":"ok"}
WebSocket: /v1/realtime
เข้ากันได้โดยตรงกับ OpenAI Realtime API — schema event JSON เดียวกัน (session.update, input_audio_buffer.append, response.create, response.audio.delta, …) กับเสียง PCM16 เข้ารหัส base64 ที่ 24 kHz client ที่เขียนสำหรับ Realtime SDK ของ OpenAI ทำงานกับ speech-server โดยไม่ต้องเปลี่ยน code (แค่เปลี่ยน URL ของ WebSocket)
ตัวอย่าง 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())
หมายเหตุการ deploy
- ไม่มีการยืนยันตัวตนตามค่าเริ่มต้น
speech-serverbind ที่127.0.0.1และเชื่อใจ caller ทุกตัว วางไว้หลัง reverse proxy (Caddy, nginx, tailscale) หากคุณต้องการเปิดสู่ภายนอก localhost - โมเดลถูกโหลดแบบ lazy request แรกสำหรับ
/transcribeจะกระตุ้นการดาวน์โหลด + โหลด ASR (~700 MB, 30–60 วินาที) ใช้--preloadเพื่อไม่มี cold start แต่ boot ช้ากว่า - ยังไม่มีการถอดเสียงแบบ streaming
POST /transcribeต้องการไฟล์ WAV ทั้งหมดตั้งแต่ต้น ใช้/v1/realtimeสำหรับ streaming - Systemd / launchd ยังไม่มี service unit แนบมาด้วย
nohup speech-server &ง่าย ๆ หรือตัว supervisor ที่คุ้นเคยก็เพียงพอ
ซอร์สโค้ด
- Sources/AudioServer — Router HTTP ที่ใช้ Hummingbird, registry โมเดลแบบโหลด lazy, handler WebSocket
/v1/realtime - Sources/AudioServerCLI — Entry point
@main, argument parser - Upstream: เอกสารอ้างอิง OpenAI Realtime API