Files
mediaplayer/sentence_api/DEPLOYMENT.md
2026-08-16 15:52:40 +08:00

11 KiB
Raw Blame History

口语训练视频服务部署指南

本文部署以下两个进程:

  • oral-trainer-api视频上传、管理后台、边界查询、Range 视频播放和朗读评分。
  • GPU 转写服务(二选一,只在服务器内网监听):
    • whisper/faster-whisperSpeaches推荐部署最简单见第 2A 节。
    • MOSS-Transcribe-Diarize:带说话人分离,见第 2 节。

推荐 Ubuntu 22.04/24.04、Python 3.12、FFmpeg、NVIDIA GPU 和已安装的 NVIDIA 驱动。MOSS 0.9B 的实际显存占用受推理框架、并发和音频长度影响,生产环境建议从 16 GB 以上显存开始验证。

1. 目录与端口

本文假设代码位于 /opt/oral-trainer

/opt/oral-trainer/
  sentence_api/
    data/
      v/                 上传的视频
      work/              临时转码文件
      attempts/          可选的学生录音
      oral_trainer.sqlite3

端口规划:

服务 监听地址 用途
MOSS 127.0.0.1:8001 内部语音转写
Whisper 127.0.0.1:9000 内部语音转写Whisper 方案,见第 2A 节)
FastAPI 127.0.0.1:8000 内部应用服务
Nginx 0.0.0.0:443 对外 HTTPS

不要把 MOSS/Whisper 的转写端口直接暴露到公网。

2A. 部署 Whisper推荐

基于 faster-whisperSpeaches的 OpenAI 兼容转写服务,接口与 MOSS 相同 POST /v1/audio/transcriptions,支持 verbose_json),因此接入 sentence_api 时应用代码无需改动。部署文件在仓库的 whisper/ 目录。

前置条件Docker、NVIDIA 驱动(≥ 535支持 CUDA 12.x和 NVIDIA Container Toolkit。 镜像自带 CUDA 12.6 运行时,宿主机不需要再装 CUDA。

sudo mkdir -p /opt/whisper
sudo chown "$USER":"$USER" /opt/whisper
cp -r whisper/* /opt/whisper/
cd /opt/whisper

cp .env.example .env          # 按需修改 WHISPER_MODEL
docker compose up -d
docker compose logs -f whisper   # 首次启动下载模型(约 3 GB

验证:

/opt/whisper/verify.sh

注册为 systemd 服务(可选,模板位于 whisper/systemd/

sudo cp whisper/systemd/whisper-transcribe.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now whisper-transcribe
sudo journalctl -u whisper-transcribe -f

接入 API 时,把 .env 改为:

MOSS_TRANSCRIBE_URL=http://127.0.0.1:9000/v1/audio/transcriptions
MOSS_MODEL=Systran/faster-whisper-large-v3

变量名沿用 MOSS_* 前缀,实际指向 Whisper 即可。模型选型与常见问题见 whisper/README.md

2. 部署 MOSS

官方模型为 OpenMOSS-Team/MOSS-Transcribe-Diarize。CUDA 12 环境可使用官方当前说明中的 vLLM 构建:

sudo apt update
sudo apt install -y ffmpeg git curl
curl -LsSf https://astral.sh/uv/install.sh | sh

sudo mkdir -p /opt/moss-transcribe
sudo chown "$USER":"$USER" /opt/moss-transcribe
cd /opt/moss-transcribe

uv venv --python 3.12 .venv
. .venv/bin/activate
uv pip install -U vllm \
  --torch-backend=auto \
  --extra-index-url https://wheels.vllm.ai/68b4a1d582818e67adc903bf1b8fc5a5447da2fa/cu129

启动模型:

. /opt/moss-transcribe/.venv/bin/activate
vllm serve OpenMOSS-Team/MOSS-Transcribe-Diarize \
  --host 127.0.0.1 \
  --port 8001 \
  --trust-remote-code

生产环境可把它注册为 systemd 服务:

# /etc/systemd/system/moss-transcribe.service
[Unit]
Description=MOSS Transcribe Service
After=network-online.target

[Service]
Type=simple
User=oraltrainer
WorkingDirectory=/opt/moss-transcribe
ExecStart=/opt/moss-transcribe/.venv/bin/vllm serve OpenMOSS-Team/MOSS-Transcribe-Diarize --host 127.0.0.1 --port 8001 --trust-remote-code
Restart=on-failure
RestartSec=10
TimeoutStartSec=1800

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now moss-transcribe
sudo journalctl -u moss-transcribe -f

模型第一次启动会下载权重。CUDA 13 服务器也可以按照 MOSS 官方仓库说明改用 SGLang Omni。推理框架的安装地址可能随上游版本更新正式部署前应对照 MOSS 官方仓库 的 Quickstart。

验证:

curl -X POST http://127.0.0.1:8001/v1/audio/transcriptions \
  -F model=OpenMOSS-Team/MOSS-Transcribe-Diarize \
  -F file=@/path/to/test.wav \
  -F response_format=verbose_json \
  -F temperature=0

响应应包含 text;开启 verbose_json 后应包含带 startendtextsegments

3. 部署 API

Docker Compose 方式

cd /opt/oral-trainer/sentence_api
cp .env.example .env
openssl rand -hex 32

分别生成两个随机值,填入 .envADMIN_API_KEYCLIENT_API_KEY,并确认:

PUBLIC_BASE_URL=https://video_service.d1kt.cn
MOSS_TRANSCRIBE_URL=http://127.0.0.1:8001/v1/audio/transcriptions

若使用 Whisper 方案(第 2A 节),把 MOSS_TRANSCRIBE_URL 改为 http://127.0.0.1:9000/v1/audio/transcriptions,并把 MOSS_MODEL 设为 Systran/faster-whisper-large-v3

启动:

docker compose up -d --build
docker compose logs -f oral-trainer-api

Compose 配置使用 Linux 的 host network使容器可以访问只监听本机的 转写服务MOSS 127.0.0.1:8001 或 Whisper 127.0.0.1:9000),同时 API 也只监听 127.0.0.1:8000。如果在 Docker Desktop 上做本地测试,可移除 network_mode: host,恢复端口映射,并把 转写服务地址改为 host.docker.internal

检查:

curl http://127.0.0.1:8000/healthz

moss_configured 应为 true。这里只表示地址已配置,实际连通性会在上传视频或评分时验证。

不使用 Docker

cd /opt/oral-trainer
python3.12 -m venv .venv-api
. .venv-api/bin/activate
python -m pip install -r sentence_api/requirements.txt

export ORAL_TRAINER_DATA_DIR=/opt/oral-trainer-data
export PUBLIC_BASE_URL=https://video_service.d1kt.cn
export ADMIN_API_KEY='替换为随机密钥'
export MOSS_TRANSCRIBE_URL=http://127.0.0.1:8001/v1/audio/transcriptions

python -m uvicorn sentence_api.main:app \
  --host 127.0.0.1 --port 8000 --workers 1 \
  --proxy-headers --forwarded-allow-ips='*'

不使用 Docker 时可创建 API 的 systemd 服务:

# /etc/systemd/system/oral-trainer-api.service
[Unit]
Description=Oral Trainer Video API
After=network-online.target moss-transcribe.service

[Service]
Type=simple
User=oraltrainer
WorkingDirectory=/opt/oral-trainer
EnvironmentFile=/opt/oral-trainer/sentence_api/.env
ExecStart=/opt/oral-trainer/.venv-api/bin/python -m uvicorn sentence_api.main:app --host 127.0.0.1 --port 8000 --workers 1 --proxy-headers --forwarded-allow-ips=*
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now oral-trainer-api
sudo journalctl -u oral-trainer-api -f

当前上传后的处理任务运行在 API 进程中,因此只能使用一个 Uvicorn worker。需要多机或多 worker 时,应把 VideoProcessor.process 迁移到 Celery、RQ 或其他持久化任务队列。

4. 配置 Nginx 与 HTTPS

sudo cp sentence_api/nginx.conf.example /etc/nginx/sites-available/oral-trainer
sudo ln -s /etc/nginx/sites-available/oral-trainer /etc/nginx/sites-enabled/oral-trainer
sudo nginx -t
sudo systemctl reload nginx

配置文件中的关键项:

  • client_max_body_size 12g:允许超过 1 GB 的视频。
  • proxy_request_buffering off:上传时直接流向 FastAPI避免 Nginx 再完整缓存一份。
  • proxy_force_ranges on:保留 Android 随机拖动播放所需的 HTTP Range。
  • proxy_read_timeout 7200s:允许长视频处理和慢速上传。

签发证书:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d video_service.d1kt.cn

证书完成后验证:

curl https://video_service.d1kt.cn/healthz

管理后台地址:

https://video_service.d1kt.cn/admin

后台的管理密钥保存在当前浏览器的 localStorage,接口请求通过 X-Admin-Key 发送。 CLIENT_API_KEY 应通过 Android 构建配置注入 OralTrainerSdkConfig.assessmentApiKey 不要硬编码进公开代码仓库。SDK 会在评分请求中发送 X-Client-Key。公开视频列表和播放 接口仍可交给 CDN 缓存GPU 评分接口则受到密钥保护。

5. 上传与处理流程

后台上传后,服务会:

  1. 管理后台通过 raw body 流式写入临时文件并同步计算 SHA-256不会先在系统临时目录保留完整副本。兼容的 multipart 接口仍以 4 MB 分块复制。
  2. 保存到 data/v/{sha256}.{extension}
  3. 使用 FFmpeg 提取 16 kHz 单声道 WAV。
  4. 请求转写服务MOSS 或 Whisperverbose_json 接口。
  5. 以转写时间戳生成句子边界,并计算每句原音有效语音时长。
  6. 把视频和句子写入 SQLite状态变为 ready

查询处理状态:

curl https://video_service.d1kt.cn/api/v1/videos
curl https://video_service.d1kt.cn/api/v1/videos/{sha256}

如果转写服务未配置,上传仍会使用原来的静音检测生成边界,但句子文本为空,无法开始测验。配置好转写服务后可在后台点击“重新处理”。

6. 视频随机播放

移动端播放地址为:

GET /api/v1/videos/{sha256}/content

服务支持 HTTP RangeMedia3/ExoPlayer 可以随机 seek并继续使用 SDK 现有的本地缓存。第一阶段不必强制改成 HLS。

上传前建议把 MP4 处理成 H.264/AAC 并把 moov 元数据移动到文件头:

ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4

如果原始编码不被 Android 广泛支持,再转码:

ffmpeg -i input.mkv \
  -c:v libx264 -preset medium -crf 22 \
  -c:a aac -b:a 128k -movflags +faststart output.mp4

当并发量明显增长或网络波动较大时,再增加 HLS/DASH 转码和对象存储/CDN。

7. 朗读评分接口

curl -X POST \
  https://video_service.d1kt.cn/api/v1/videos/{sha256}/sentences/0/assessments \
  -H 'X-Client-Key: 替换为客户端密钥' \
  -F audio=@student.wav \
  -F language=en

当前 asr-fluency-v1 评分为:

总分 = 内容分 * 80% + 流畅度 * 20%
流畅度 = 有效语音时长分 * 35% + 停顿分 * 40% + 语速分 * 25%

总分不低于 ASSESSMENT_PASS_SCORE(默认 70即通过。pronunciation_scoreprosody_score 目前返回 null,避免把 ASR 内容匹配误报为音素发音质量。

服务只向转写服务发送语言提示,不发送标准句子作为 prompt 或热词,避免标准答案诱导识别结果。

8. 运维与备份

需要持久化备份:

sentence_api/data/v/
sentence_api/data/oral_trainer.sqlite3

work/ 可以清理。KEEP_ATTEMPT_AUDIO=false 时学生录音在评分完成后自动删除;设为 true 时还需备份和定期清理 attempts/,并在产品隐私政策中说明保存期限。

SQLite 在线备份示例:

sqlite3 sentence_api/data/oral_trainer.sqlite3 \
  ".backup '/backup/oral_trainer-$(date +%F).sqlite3'"

升级前先备份数据库和 data/v/。API 重启期间被中断的视频会保留 processing 状态,可从后台执行“重新处理”。