Распознавание речи на Apple Neural Engine — parakeet-v3 через FluidAudio CoreML
Benchmark →Подключение: ws://HOST:8765. Все команды отправляются JSON, аудио — бинарными фреймами (PCM16 LE, 16kHz, mono).
Открыть WebSocket, отправить команду start, затем слать бинарные чанки PCM16. Текст приходит в реальном времени.
// Начать стрим
ws.send(JSON.stringify({ action: "start" }))
// Отправлять PCM16 каждые 100мс (1600 сэмплов = 3200 байт)
ws.send(pcm16_chunk) // ArrayBuffer или bytes
// Получаем транскрипцию (приходит автоматически)
// { "type": "transcription", "text": "накопленный текст", "delta": "новый фрагмент" }
// Остановить стрим
ws.send(JSON.stringify({ action: "stop" }))
// Сбросить накопленный текст
ws.send(JSON.stringify({ action: "reset" }))
Отправить метаданные JSON, затем сам файл бинарным фреймом. Поддерживаются MP3, M4A, WAV, FLAC (без ffmpeg).
// Шаг 1: отправить метаданные
ws.send(JSON.stringify({
action: "transcribe_file",
filename: "audio.m4a",
size: 5937694
}))
// Шаг 2: отправить файл целиком
ws.send(file_bytes) // ArrayBuffer
// Ответ:
// {
// "type": "file_transcription",
// "text": "распознанный текст...",
// "transcription_time": 3.535,
// "worker": 0,
// "mode": "single",
// "pool_size": 1
// }
single — один процесс, все запросы в очереди. pool — N процессов, параллельная обработка.
ws.send(JSON.stringify({
action: "set_mode",
mode: "pool", // "single" или "pool"
pool_size: 2 // кол-во воркеров (1-5)
}))
// Ответ (broadcast всем клиентам):
// { "type": "status", "mode": "pool", "pool_size": 2, "pool_active": 0 }
ws.send(JSON.stringify({ action: "status" }))
// Ответ:
// {
// "type": "status",
// "streaming": false,
// "model": "parakeet-v3",
// "engine": "FluidAudio CoreML",
// "mode": "single",
// "pool_size": 1,
// "pool_active": 0
// }
import asyncio, websockets, json
async def transcribe_file(path):
async with websockets.connect("ws://localhost:8765",
max_size=200*1024*1024) as ws:
with open(path, "rb") as f:
data = f.read()
ws.send(json.dumps({
"action": "transcribe_file",
"filename": path.split("/")[-1],
"size": len(data)
}))
ws.send(data)
while True:
msg = json.loads(await ws.recv())
if msg["type"] == "file_transcription":
print(f"Текст: {msg['text'][:100]}...")
print(f"Время: {msg['transcription_time']:.3f}s")
break
asyncio.run(transcribe_file("audio.m4a"))
# Single режим (по умолчанию)
python3 server.py
# Pool режим с 3 воркерами
python3 server.py --mode pool --pool-size 3
# Все параметры
python3 server.py --host 0.0.0.0 --port 8765 --mode pool --pool-size 2
Сервер можно зарегистрировать как службу macOS — он будет автоматически запускаться при загрузке системы и перезапускаться при падении.
# 1. Создать plist файл
cat > ~/Library/LaunchAgents/com.redpax.argmax-server.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.redpax.argmax-server</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/.venv/bin/python3</string>
<string>/path/to/argmax/server.py</string>
<string>--host</string>
<string>0.0.0.0</string>
<string>--port</string>
<string>8767</string>
</array>
<key>WorkingDirectory</key>
<string>/path/to/argmax</string>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key><false/>
<key>Crashed</key><true/>
</dict>
<key>ThrottleInterval</key><integer>10</integer>
<key>StandardOutPath</key>
<string>/path/to/argmax/launchd-stdout.log</string>
<key>StandardErrorPath</key>
<string>/path/to/argmax/launchd-stderr.log</string>
</dict>
</plist>
EOF
# 2. Загрузить службу
launchctl load ~/Library/LaunchAgents/com.redpax.argmax-server.plist
# 3. Проверить статус
launchctl list | grep argmax
# 4. Остановить / перезапустить
launchctl unload ~/Library/LaunchAgents/com.redpax.argmax-server.plist
launchctl load ~/Library/LaunchAgents/com.redpax.argmax-server.plist
# 5. Логи
tail -f /path/to/argmax/launchd-stderr.log
RunAtLoad — запуск при входе пользователя. KeepAlive/Crashed — автоперезапуск при падении. ThrottleInterval=10 — минимум 10 сек между перезапусками.