Spaces:
Running
Running
File size: 18,195 Bytes
28892a1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 |
# Seed-VC Streaming API 仕様書
## 概要
Seed-VC Streaming APIは、ゼロショット音声変換をチャンク単位で処理するHTTP APIサーバーです。
クライアントが音声を小さなチャンク(例: 500ms)に分割して順次送信し、サーバーが各チャンクを変換して返すことで、低レイテンシなストリーミング処理を実現します。
- **ベースURL**: `https://akatuki25-seed-vc-streaming.hf.space`
- **モデル**: Seed-VC (Plachtaa/seed-vc)
- **入力サンプルレート**: 16000Hz (推奨)
- **出力サンプルレート**: 22050Hz
- **推奨チャンクサイズ**: 500ms (overlap 100ms)
- **プリセット参照音声**: デフォルトで`default_female`が利用可能(カスタム音声のアップロードも可)
---
## アーキテクチャ
### ストリーミング処理フロー
```
クライアント側:
1. 音声をチャンク分割 (500ms × N個)
2. セッション作成 → session_id取得
3. (オプション) カスタム参照音声アップロード
※プリセット参照音声を使う場合はスキップ
4. チャンクを順次送信 (chunk_0, chunk_1, ...)
5. 各レスポンスを受信・結合
6. セッション終了
サーバー側:
1. セッション管理 (参照音声の特徴量をキャッシュ)
※プリセット使用時はHF Datasetから自動ダウンロード
2. 各チャンクを独立に変換
3. クロスフェード処理 (overlap_msで指定)
4. 変換後チャンクを即座に返却
```
### 重要な設計ポイント
- **チャンク単位処理**: `/chunk`エンドポイントは1回のリクエストで1チャンクのみ処理・返却
- **クライアント側結合**: 全チャンクを受信後、クライアントが`np.concatenate()`等で結合
- **サーバー側クロスフェード**: `overlap_ms`で指定した重複部分を自動的にクロスフェード
- **セッション状態**: 参照音声の特徴量、前回チャンクの末尾を保持
---
## エンドポイント仕様
### 1. GET /health
ヘルスチェック用エンドポイント
**リクエスト**
```bash
GET /health
```
**レスポンス**
```json
{
"status": "ok"
}
```
---
### 2. POST /session
新しい変換セッションを作成
**リクエスト**
```bash
POST /session
Content-Type: application/json
{
"sample_rate": 16000,
"tgt_speaker_id": null,
"ref_preset_id": null,
"use_uploaded_ref": true,
"chunk_len_ms": 500,
"overlap_ms": 100
}
```
**パラメータ**
| フィールド | 型 | 必須 | デフォルト | 説明 |
|-----------|-----|------|-----------|------|
| `sample_rate` | int | No | 16000 | 入力音声のサンプルレート (Hz) |
| `tgt_speaker_id` | str | No | null | ターゲット話者ID (未使用) |
| `ref_preset_id` | str | No | "default_female" | プリセット参照音声ID ("default_female", "default_male") |
| `use_uploaded_ref` | bool | No | false | 参照音声をアップロードする場合true。falseの場合はref_preset_idを使用 |
| `chunk_len_ms` | int | No | 1000 | チャンク長 (ミリ秒) |
| `overlap_ms` | int | No | 200 | チャンク間のオーバーラップ (ミリ秒) |
**レスポンス**
```json
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"sample_rate": 16000,
"chunk_len_ms": 500,
"overlap_ms": 100
}
```
---
### 3. POST /session/ref
参照音声(ターゲット話者音声)をアップロード
**リクエスト**
```bash
POST /session/ref
Content-Type: multipart/form-data
session_id: <session_id>
ref_audio: <WAVファイル>
```
**パラメータ**
| フィールド | 型 | 必須 | 説明 |
|-----------|-----|------|------|
| `session_id` | str | Yes | セッションID |
| `ref_audio` | file | Yes | 参照音声WAVファイル (任意のサンプルレート、自動リサンプル) |
**レスポンス**
```json
{
"status": "ok"
}
```
**処理内容**
- 参照音声を22050Hzにリサンプル
- 最大25秒に切り詰め
- Whisperセマンティック特徴量を抽出
- CAMPPlusスタイル埋め込みを計算
- メルスペクトログラムを生成
- セッションに紐付けて保存
---
### 4. POST /chunk
音声チャンクを変換
**リクエスト**
```bash
POST /chunk
Content-Type: multipart/form-data
session_id: <session_id>
chunk_id: <chunk_id>
audio: <WAVファイル>
```
**パラメータ**
| フィールド | 型 | 必須 | 説明 |
|-----------|-----|------|------|
| `session_id` | str | Yes | セッションID |
| `chunk_id` | int | Yes | チャンクID (0始まりの連番) |
| `audio` | file | Yes | 音声チャンクWAVファイル |
**レスポンス**
```
Content-Type: audio/wav
X-Chunk-Id: <chunk_id>
<WAVバイナリデータ>
```
**処理フロー**
1. 音声チャンクを読み込み (セッションのsample_rateと一致確認)
2. Seed-VCで音声変換
- Whisperセマンティック特徴抽出
- Length Regulator適用
- CFM (Conditional Flow Matching) で推論
- BigVGAN Vocoderで音声生成
3. 前回チャンクの末尾とクロスフェード (`overlap_ms`分)
4. 変換後チャンクを返却 (22050Hz WAV)
**重要**: このエンドポイントは**1チャンクのみ**を返します。全体音声を得るにはクライアント側で結合が必要です。
---
### 5. POST /end
セッションを終了
**リクエスト**
```bash
POST /end
Content-Type: application/json
{
"session_id": "<session_id>"
}
```
**レスポンス**
```json
{
"status": "ended"
}
```
---
## 使用例
### Python完全実装例
#### パターンA: プリセット参照音声を使用(推奨)
```python
import requests
import numpy as np
import soundfile as sf
import io
# ====================
# 設定
# ====================
API_BASE = "https://akatuki25-seed-vc-streaming.hf.space"
SOURCE_AUDIO = "source.wav" # 変換したい音声
OUTPUT_AUDIO = "output.wav"
SAMPLE_RATE = 16000
CHUNK_LEN_MS = 500
OVERLAP_MS = 100
# ====================
# 1. 音声読み込み
# ====================
source, sr = sf.read(SOURCE_AUDIO)
if sr != SAMPLE_RATE:
import librosa
source = librosa.resample(source, orig_sr=sr, target_sr=SAMPLE_RATE)
# ====================
# 2. セッション作成(プリセット参照音声使用)
# ====================
resp = requests.post(f"{API_BASE}/session", json={
"sample_rate": SAMPLE_RATE,
"use_uploaded_ref": False, # プリセットを使用
"ref_preset_id": "default_female", # 省略可(デフォルト)
"chunk_len_ms": CHUNK_LEN_MS,
"overlap_ms": OVERLAP_MS
})
session_id = resp.json()["session_id"]
print(f"Session created: {session_id}")
# 3. 参照音声アップロードは不要(プリセット使用時)
# ====================
# 4. チャンク分割
# ====================
chunk_len_samples = int(SAMPLE_RATE * CHUNK_LEN_MS / 1000)
chunks = []
for i in range(0, len(source), chunk_len_samples):
chunk = source[i:i + chunk_len_samples]
chunks.append(chunk)
print(f"Split into {len(chunks)} chunks")
# ====================
# 5. チャンク順次送信・受信
# ====================
output_chunks = []
for chunk_id, chunk in enumerate(chunks):
# WAVバイト列に変換
buffer = io.BytesIO()
sf.write(buffer, chunk, SAMPLE_RATE, format="WAV", subtype="PCM_16")
buffer.seek(0)
# POSTリクエスト
resp = requests.post(f"{API_BASE}/chunk",
data={"session_id": session_id, "chunk_id": chunk_id},
files={"audio": ("chunk.wav", buffer, "audio/wav")})
# 変換後チャンク取得
converted_chunk, conv_sr = sf.read(io.BytesIO(resp.content))
output_chunks.append(converted_chunk)
print(f"Chunk {chunk_id}/{len(chunks)-1} processed")
# ====================
# 6. チャンク結合
# ====================
output_audio = np.concatenate(output_chunks)
sf.write(OUTPUT_AUDIO, output_audio, 22050)
print(f"Output saved: {OUTPUT_AUDIO}")
# ====================
# 7. セッション終了
# ====================
requests.post(f"{API_BASE}/end", json={"session_id": session_id})
print("Session ended")
```
#### パターンB: カスタム参照音声をアップロード
```python
import requests
import numpy as np
import soundfile as sf
import io
# ====================
# 設定
# ====================
API_BASE = "https://akatuki25-seed-vc-streaming.hf.space"
SOURCE_AUDIO = "source.wav" # 変換したい音声
REF_AUDIO = "target_speaker.wav" # ターゲット話者の参照音声
OUTPUT_AUDIO = "output.wav"
SAMPLE_RATE = 16000
CHUNK_LEN_MS = 500
OVERLAP_MS = 100
# ====================
# 1. 音声読み込み
# ====================
source, sr = sf.read(SOURCE_AUDIO)
if sr != SAMPLE_RATE:
import librosa
source = librosa.resample(source, orig_sr=sr, target_sr=SAMPLE_RATE)
# ====================
# 2. セッション作成(カスタム参照音声)
# ====================
resp = requests.post(f"{API_BASE}/session", json={
"sample_rate": SAMPLE_RATE,
"use_uploaded_ref": True, # カスタム参照音声を使用
"chunk_len_ms": CHUNK_LEN_MS,
"overlap_ms": OVERLAP_MS
})
session_id = resp.json()["session_id"]
print(f"Session created: {session_id}")
# ====================
# 3. 参照音声アップロード
# ====================
with open(REF_AUDIO, "rb") as f:
resp = requests.post(f"{API_BASE}/session/ref",
data={"session_id": session_id},
files={"ref_audio": f})
print("Reference audio uploaded")
# 4〜7は同じ (チャンク分割、送信、結合、終了)
# ...
```
### curlを使った例
#### パターンA: プリセット参照音声を使用
```bash
#!/bin/bash
API_BASE="https://akatuki25-seed-vc-streaming.hf.space"
# 1. セッション作成(プリセット参照音声使用)
SESSION=$(curl -s -X POST "$API_BASE/session" \
-H "Content-Type: application/json" \
-d '{"sample_rate":16000,"use_uploaded_ref":false,"ref_preset_id":"default_female","chunk_len_ms":500,"overlap_ms":100}' \
| jq -r '.session_id')
echo "Session: $SESSION"
# 2. 参照音声アップロードは不要
# 3. チャンク送信 (例: chunk_0)
curl -X POST "$API_BASE/chunk" \
-F "session_id=$SESSION" \
-F "chunk_id=0" \
-F "audio=@chunk_0.wav" \
-o output_chunk_0.wav
# 4. セッション終了
curl -X POST "$API_BASE/end" \
-H "Content-Type: application/json" \
-d "{\"session_id\":\"$SESSION\"}"
```
#### パターンB: カスタム参照音声をアップロード
```bash
#!/bin/bash
API_BASE="https://akatuki25-seed-vc-streaming.hf.space"
# 1. セッション作成
SESSION=$(curl -s -X POST "$API_BASE/session" \
-H "Content-Type: application/json" \
-d '{"sample_rate":16000,"use_uploaded_ref":true,"chunk_len_ms":500,"overlap_ms":100}' \
| jq -r '.session_id')
echo "Session: $SESSION"
# 2. 参照音声アップロード
curl -X POST "$API_BASE/session/ref" \
-F "session_id=$SESSION" \
-F "ref_audio=@target_speaker.wav"
# 3. チャンク送信 (例: chunk_0)
curl -X POST "$API_BASE/chunk" \
-F "session_id=$SESSION" \
-F "chunk_id=0" \
-F "audio=@chunk_0.wav" \
-o output_chunk_0.wav
# 4. セッション終了
curl -X POST "$API_BASE/end" \
-H "Content-Type: application/json" \
-d "{\"session_id\":\"$SESSION\"}"
```
---
## クロスフェード処理
サーバー側で自動的に処理されます。
### 仕組み
```
チャンク0: [=============================]
↓ overlap_ms (100ms)
チャンク1: [=============================]
|<-fade->|
出力0: [========================] (fade-outなし)
出力1: [==|fade-in|==================]
最終結合: [========================================]
```
### パラメータ調整
| overlap_ms | 効果 | 推奨用途 |
|-----------|------|---------|
| 0 | クロスフェードなし | デバッグ用 |
| 50 | 最小限の平滑化 | 超低レイテンシ優先 |
| 100 | 標準 | バランス型 |
| 200 | 高品質 | 音質優先 |
---
## パフォーマンス特性
### レイテンシ測定結果
**環境**: Hugging Face Spaces (NVIDIA T4 GPU)
| チャンクサイズ | 初回処理時間 | 2回目以降 | RTF (Real-Time Factor) |
|--------------|-------------|----------|----------------------|
| 100ms | ~2.0秒 | ~0.5秒 | ~5.0x |
| 200ms | ~2.0秒 | ~0.7秒 | ~3.5x |
| 500ms | ~2.0秒 | ~1.0秒 | ~2.0x |
| 1000ms | ~2.5秒 | ~1.5秒 | ~1.5x |
**RTF**: レイテンシ ÷ 入力音声長。1.0未満でリアルタイム処理可能。
### 推奨設定
```json
{
"chunk_len_ms": 500,
"overlap_ms": 100
}
```
**理由**:
- 初回ウォームアップ後、RTF ~2.0x (実用的)
- 適度なクロスフェード品質
- ネットワークオーバーヘッドとのバランス
---
## エラーハンドリング
### HTTP 400 エラー
```json
{
"detail": "Invalid session_id"
}
```
**原因**:
- セッションIDが存在しない
- セッションが期限切れ (600秒無操作)
**対処**: 新しいセッションを作成
---
```json
{
"detail": "Sample rate mismatch: expected 16000, got 44100"
}
```
**原因**: チャンクのサンプルレートがセッション作成時と異なる
**対処**: 音声を正しいサンプルレートにリサンプル
---
### HTTP 500 エラー
**原因**: サーバー内部エラー (モデル推論失敗等)
**対処**:
1. チャンク長を変更して再試行
2. 参照音声を別のものに変更
3. 数秒待ってリトライ
---
## ベストプラクティス
### 1. 参照音声の選び方
#### プリセット参照音声を使う場合(推奨)
```python
# デフォルトプリセット使用(最も簡単)
resp = requests.post(f"{API_BASE}/session", json={
"sample_rate": 16000,
"use_uploaded_ref": False # プリセット使用
})
# または明示的に指定
resp = requests.post(f"{API_BASE}/session", json={
"sample_rate": 16000,
"use_uploaded_ref": False,
"ref_preset_id": "default_female" # or "default_male"
})
```
**メリット**:
- アップロード不要で即座に利用可能
- 安定した品質の参照音声
- ネットワーク帯域を節約
#### カスタム参照音声をアップロードする場合
- **長さ**: 3〜10秒推奨 (最大25秒まで自動切り詰め)
- **品質**: クリーンな音声 (ノイズ・エコー少ない)
- **内容**: 単一話者、自然な発話
### 2. チャンク分割
```python
# ❌ 悪い例: オーバーラップ考慮なし
chunks = [audio[i:i+chunk_len] for i in range(0, len(audio), chunk_len)]
# ✅ 良い例: オーバーラップなし(サーバー側で処理)
chunk_len_samples = int(SAMPLE_RATE * CHUNK_LEN_MS / 1000)
chunks = [audio[i:i+chunk_len_samples]
for i in range(0, len(audio), chunk_len_samples)]
```
**重要**: クライアント側でオーバーラップを持たせる必要はありません。サーバーが前回チャンクの末尾を保持してクロスフェード処理します。
### 3. セッション管理
```python
# セッション再利用(同一話者の複数音声変換)
for source_file in source_files:
# チャンク処理...
pass
# 最後に1回だけ終了
requests.post(f"{API_BASE}/end", json={"session_id": session_id})
```
### 4. エラーリトライ
```python
import time
MAX_RETRIES = 3
for attempt in range(MAX_RETRIES):
try:
resp = requests.post(f"{API_BASE}/chunk", ...)
resp.raise_for_status()
break
except requests.RequestException as e:
if attempt == MAX_RETRIES - 1:
raise
time.sleep(2 ** attempt) # Exponential backoff
```
---
## 技術詳細
### モデルコンポーネント
1. **Whisper (semantic feature extractor)**
- 入力: 16kHz音声
- 出力: セマンティック特徴量
2. **CAMPPlus (speaker encoder)**
- 入力: 16kHz音声のFbank特徴量
- 出力: 話者埋め込みベクトル
3. **DiT-based Flow Matching Model**
- 入力: セマンティック特徴 + 話者埋め込み
- 出力: メルスペクトログラム
- 推論ステップ数: 10
- CFG rate: 0.7
4. **BigVGAN Vocoder**
- 入力: メルスペクトログラム
- 出力: 22050Hz音声波形
### サンプルレート変換フロー
```
入力音声 (16kHz)
↓
Seed-VC内部リサンプル (22050Hz)
↓
Whisper用ダウンサンプル (16kHz)
↓
推論処理 (22050Hz mel)
↓
Vocoder出力 (22050Hz)
```
---
## 制限事項
1. **リアルタイム性**: GPU環境でもRTF > 1.0 (完全なリアルタイム処理は不可)
2. **セッションタイムアウト**: 600秒無操作で自動削除
3. **参照音声長**: 最大25秒まで
4. **同時セッション数**: Hugging Face Spacesの制限に依存
5. **GPU必須**: CPU環境ではRTF 20〜60x (実用不可)
---
## FAQ
### Q: チャンクサイズを小さくすればレイテンシは下がる?
A: 初回コールドスタートのオーバーヘッド(~2秒)が支配的なため、100ms以下にしても劇的な改善はありません。500msが推奨です。
### Q: クライアント側でクロスフェードする必要は?
A: 不要です。サーバーが`overlap_ms`に基づいて自動処理します。受信したチャンクをそのまま結合してください。
### Q: 複数セッションを同時に使える?
A: 可能ですが、各セッションは独立してGPUメモリを消費します。Hugging Face Spacesの無料枠では同時1〜2セッションが現実的です。
### Q: CPUモードで動作する?
A: 動作しますが、RTF 20〜60xと実用的ではありません。GPU環境必須です。
---
## サポート・問い合わせ
- **リポジトリ**: https://huggingface.co/spaces/akatukiseed/seed-vc-streaming
- **ベースモデル**: https://github.com/Plachtaa/seed-vc
- **Hugging Face Space**: https://akatukiseed-seed-vc-streaming.hf.space
---
## 変更履歴
| バージョン | 日付 | 変更内容 |
|----------|------|---------|
| 1.0.0 | 2025-11-22 | 初版リリース |
|