Spaces:
Running
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
ヘルスチェック用エンドポイント
リクエスト
GET /health
レスポンス
{
"status": "ok"
}
2. POST /session
新しい変換セッションを作成
リクエスト
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 | チャンク間のオーバーラップ (ミリ秒) |
レスポンス
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"sample_rate": 16000,
"chunk_len_ms": 500,
"overlap_ms": 100
}
3. POST /session/ref
参照音声(ターゲット話者音声)をアップロード
リクエスト
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ファイル (任意のサンプルレート、自動リサンプル) |
レスポンス
{
"status": "ok"
}
処理内容
- 参照音声を22050Hzにリサンプル
- 最大25秒に切り詰め
- Whisperセマンティック特徴量を抽出
- CAMPPlusスタイル埋め込みを計算
- メルスペクトログラムを生成
- セッションに紐付けて保存
4. POST /chunk
音声チャンクを変換
リクエスト
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バイナリデータ>
処理フロー
- 音声チャンクを読み込み (セッションのsample_rateと一致確認)
- Seed-VCで音声変換
- Whisperセマンティック特徴抽出
- Length Regulator適用
- CFM (Conditional Flow Matching) で推論
- BigVGAN Vocoderで音声生成
- 前回チャンクの末尾とクロスフェード (
overlap_ms分) - 変換後チャンクを返却 (22050Hz WAV)
重要: このエンドポイントは1チャンクのみを返します。全体音声を得るにはクライアント側で結合が必要です。
5. POST /end
セッションを終了
リクエスト
POST /end
Content-Type: application/json
{
"session_id": "<session_id>"
}
レスポンス
{
"status": "ended"
}
使用例
Python完全実装例
パターンA: プリセット参照音声を使用(推奨)
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: カスタム参照音声をアップロード
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: プリセット参照音声を使用
#!/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: カスタム参照音声をアップロード
#!/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未満でリアルタイム処理可能。
推奨設定
{
"chunk_len_ms": 500,
"overlap_ms": 100
}
理由:
- 初回ウォームアップ後、RTF ~2.0x (実用的)
- 適度なクロスフェード品質
- ネットワークオーバーヘッドとのバランス
エラーハンドリング
HTTP 400 エラー
{
"detail": "Invalid session_id"
}
原因:
- セッションIDが存在しない
- セッションが期限切れ (600秒無操作)
対処: 新しいセッションを作成
{
"detail": "Sample rate mismatch: expected 16000, got 44100"
}
原因: チャンクのサンプルレートがセッション作成時と異なる
対処: 音声を正しいサンプルレートにリサンプル
HTTP 500 エラー
原因: サーバー内部エラー (モデル推論失敗等)
対処:
- チャンク長を変更して再試行
- 参照音声を別のものに変更
- 数秒待ってリトライ
ベストプラクティス
1. 参照音声の選び方
プリセット参照音声を使う場合(推奨)
# デフォルトプリセット使用(最も簡単)
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. チャンク分割
# ❌ 悪い例: オーバーラップ考慮なし
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. セッション管理
# セッション再利用(同一話者の複数音声変換)
for source_file in source_files:
# チャンク処理...
pass
# 最後に1回だけ終了
requests.post(f"{API_BASE}/end", json={"session_id": session_id})
4. エラーリトライ
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
技術詳細
モデルコンポーネント
Whisper (semantic feature extractor)
- 入力: 16kHz音声
- 出力: セマンティック特徴量
CAMPPlus (speaker encoder)
- 入力: 16kHz音声のFbank特徴量
- 出力: 話者埋め込みベクトル
DiT-based Flow Matching Model
- 入力: セマンティック特徴 + 話者埋め込み
- 出力: メルスペクトログラム
- 推論ステップ数: 10
- CFG rate: 0.7
BigVGAN Vocoder
- 入力: メルスペクトログラム
- 出力: 22050Hz音声波形
サンプルレート変換フロー
入力音声 (16kHz)
↓
Seed-VC内部リサンプル (22050Hz)
↓
Whisper用ダウンサンプル (16kHz)
↓
推論処理 (22050Hz mel)
↓
Vocoder出力 (22050Hz)
制限事項
- リアルタイム性: GPU環境でもRTF > 1.0 (完全なリアルタイム処理は不可)
- セッションタイムアウト: 600秒無操作で自動削除
- 参照音声長: 最大25秒まで
- 同時セッション数: Hugging Face Spacesの制限に依存
- 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 | 初版リリース |