speech-server
speech-server là một máy chủ HTTP + WebSocket cục bộ, mở mọi mô hình Soniqo qua một REST API đơn giản, kèm theo một WebSocket tương thích OpenAI Realtime API tại /v1/realtime. Nó được đóng gói trong cùng Homebrew bottle với CLI speech — lệnh brew install speech đặt cả hai vào PATH của bạn.
Cài đặt và chạy
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)
Cờ dòng lệnh
| Cờ | Mặc định | Mô tả |
|---|---|---|
--host | 127.0.0.1 | Địa chỉ bind. Đổi sang 0.0.0.0 để mở ra trên LAN. |
--port | 8080 | Cổng TCP. |
--preload | tắt | Tải tất cả mô hình ngay khi khởi động. Boot chậm hơn (~30–60 giây) nhưng request đầu tiên không bị trễ. |
Mô hình được tải xuống ở lần dùng đầu tiên và được cache trong ~/Library/Caches/qwen3-speech/. Request đầu tiên cho một mô hình phải chịu chi phí tải xuống + nạp (30 giây – 2 phút tùy kích thước mô hình); các request tiếp theo đã ấm.
Các endpoint REST
POST /transcribe — Chuyển giọng nói thành văn bản
Chấp nhận hoặc body WAV thô, hoặc một JSON envelope với âm thanh đã mã hóa 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"}'
Phản hồi: {"text": "…", "language": "en", "confidence": 0.93}.
POST /speak — Chuyển văn bản thành giọng nói
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 phản hồi là một blob WAV. Các giá trị engine được hỗ trợ: qwen3 (mặc định), cosyvoice, kokoro.
POST /respond — Giọng nói sang giọng nói
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
Chạy PersonaPlex 7B — giọng nói vào, giọng nói ra. Transcript được trả về qua header X-Response-Text. Xem hướng dẫn PersonaPlex để biết tên các voice preset.
POST /enhance — Cải thiện giọng nói
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. Đầu vào được resample nếu cần.
GET /health — Kiểm tra hoạt động
curl http://127.0.0.1:8080/health
# {"status":"ok"}
WebSocket: /v1/realtime
Tương thích trực tiếp với OpenAI Realtime API — cùng lược đồ sự kiện JSON (session.update, input_audio_buffer.append, response.create, response.audio.delta, …) với âm thanh PCM16 mã hóa base64 ở 24 kHz. Các client được viết cho Realtime SDK của OpenAI hoạt động với speech-server mà không cần thay đổi code (chỉ cần đổi URL của WebSocket).
Ví dụ 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);
}
};
Ví dụ 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())
Ghi chú triển khai
- Không xác thực mặc định.
speech-serverbind vào127.0.0.1và tin tưởng mọi caller. Đặt nó sau một reverse proxy (Caddy, nginx, tailscale) nếu bạn muốn mở ra ngoài localhost. - Mô hình được tải lười. Request đầu tiên cho
/transcribekích hoạt tải xuống + nạp ASR (~700 MB, 30–60 giây). Dùng--preloadđể không có cold start nhưng boot chậm hơn. - Chưa có phiên âm streaming.
POST /transcribeyêu cầu toàn bộ file WAV ngay từ đầu. Dùng/v1/realtimeđể streaming. - Systemd / launchd. Hiện chưa có service unit đi kèm. Một
nohup speech-server &đơn giản hoặc trình giám sát tiến trình quen thuộc của bạn là đủ.
Mã nguồn
- Sources/AudioServer — Router HTTP dựa trên Hummingbird, registry mô hình tải lười, handler WebSocket
/v1/realtime. - Sources/AudioServerCLI — Entry point
@main, argument parser. - Upstream: tham chiếu OpenAI Realtime API.