Speech Server
Bộ chạy gốc cho Apple · PocketTTS
PocketTTS bổ sung tổng hợp giọng nói CoreML gốc cho speech-swift. Mọi giai đoạn mặc định chỉ dùng CPU. Giọng Alba tiếng Anh phát từng khung 1.920 mẫu ở 24 kHz, tương đương 80 ms mỗi khung.
Dùng pocket-tts với giọng alba. HTTP trực tiếp yêu cầu PCM; WebSocket phát các đoạn âm thanh. Các mô hình mặc định hiện có được giữ nguyên.
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 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 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"}
Các kiểm thử HTTP và WebSocket thời gian thực chờ /health trả về HTTP 200 cùng {"status":"ok"}, với thời hạn khởi động 10 giây. Lỗi khởi động làm bước thiết lập kiểm thử thất bại, và bước dọn dẹp chờ máy chủ dừng trước khi tái sử dụng cổng.
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).
Lượt nói tự động bằng VAD phía máy chủ
input_audio_buffer.commit thủ công vẫn là mặc định. Đặt session.turn_detection thành server_vad để chạy Silero VAD trên máy chủ và tự động chép lời mỗi lượt nói đã kết thúc. Âm thanh tiền tố được giữ lại, bộ đệm khi rảnh được giới hạn, các sự kiện speech_started, speech_stopped và committed được gửi theo thứ tự, còn lượt nói liên tục sẽ bị buộc kết thúc ở giới hạn đã cấu hình.
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
}
}
}));
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.
Quản lý mô hình tùy chọn
SpeechRuntime cung cấp tải dùng chung, tiếp nhận FIFO có thể hủy và thực thi tuần tự. Qwen3ASRRuntime chọn MLX hoặc CoreML đầy đủ bằng bộ mô tả rõ ràng; API gốc và cờ CLI vẫn được hỗ trợ.
Máy chủ giữ một quyền thực thi trong toàn bộ yêu cầu, kể cả đầu ra truyền trực tiếp, với tối đa 32 yêu cầu chờ. Lỗi ASR tạo lỗi HTTP hoặc sự kiện lỗi WebSocket; bản chép lời rỗng hợp lệ vẫn thành công. Phát lại giữ các mẫu dư. Mô hình lưu đệm và âm thanh đang chờ vẫn chiếm bộ nhớ.
Voxtral TTS
Voxtral 4B TTS 2603 chạy cục bộ trên Apple Silicon qua mô-đun VoxtralTTS: 20 giọng cài sẵn, chín ngôn ngữ và âm thanh đơn kênh 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.wavChỉ hỗ trợ giọng có sẵn; chưa hỗ trợ nhân bản từ âm thanh tham chiếu. SDK, CLI, WebSocket và HTTP PCM xuất âm thanh trong khi tạo. Phải cung cấp toàn bộ văn bản đầu vào ngay từ đầu. Trọng số mô hình dùng giấy phép CC BY-NC 4.0.
Prefill prompt của Gemma 4 12B
Dịch vụ suy luận gốc tải mô hình Gemma 4 12B Unified INT4 đã chuẩn bị qua agentDirectory. Để trò chuyện, chọn ID mô hình gemma4-12b-it.
Bước prefill của Gemma 4 12B điền trạng thái KV theo lô tối đa 256 token và tính logits cho lần lấy mẫu đầu tiên từ token cuối của prompt. Các phép chiếu từ vựng ở vị trí trung gian được bỏ qua. Bộ nhớ đệm KV của yêu cầu được giải phóng khi hoàn tất hoặc hủy.
Một tiền tố hệ thống/công cụ không đổi có thể được giữ theo các khối đầy đủ 256 token. Mỗi yêu cầu mở rộng bản sao làm việc riêng; tin nhắn người dùng, lịch sử hội thoại và kết quả công cụ không được thêm vào trạng thái giữ lại. SPEECH_SWIFT_GEMMA4_PREFIX_CACHE_MB giới hạn dung lượng theo MiB; 0 tắt tái sử dụng. clearPrefixCache() giải phóng trạng thái này. HTTP báo số token tái sử dụng trong cachedPrefixTokens và thông tin nguồn ghi gemma4_12b_prefix_cache_budget_bytes. Yêu cầu lạnh vẫn xử lý toàn bộ prompt.
Lịch sử công cụ gốc của Gemma giữ kết quả công cụ và phần trả lời tiếp theo của trợ lý trong cùng một lượt mô hình. Nếu sau đó có tin nhắn người dùng, lượt mô hình được đóng trước khi mở lượt người dùng tiếp theo. Gửi mỗi kết quả cùng toolCallID ban đầu của nó.