Timeline Studioに、ブラウザ内で完結する多言語ボイスクローン機能を実装しました。
参照音声をサーバーへ送信する方式ではありません。音声のデコード、話者特徴量の抽出、声質変換、試聴、保存、タイムライン上の音声置換までをブラウザ内で処理します。
GitHubリポジトリ
https://github.com/MartinDelophy/ai-video-editor
本記事では、モデル構成、音声処理、WebGPU/WASM実行、キャッシュ、後処理、FP16を採用した理由を技術記録としてまとめます。
全体のワークフロー
処理は「元音声の生成」と「声質の変換」の2段階に分けています。
テキスト入力
↓
言語とベース音声を選択
↓
TTSで元音声を生成
↓
参照音声をアップロードまたは録音
↓
話者埋め込みを抽出
↓
OpenVoice V2で声質を変換
↓
結果を試聴
↓
My assetsへ保存、または現在のクリップを置換
TTSは発音、言語、リズムを担当し、OpenVoice V2は話者の声質を移します。
日本語テキストは日本語TTS、英語テキストは英語TTSで生成してから、同じ参照音声の声質へ変換できます。保存した一つの声質を複数言語で再利用できる構成です。
使用したモデル
ブラウザ版ではOpenVoice V2 ConverterのFP16 ONNXモデルを使用しています。
モデルは次の2ファイルに分かれています。
| モデル | サイズ |
|---|---|
| Reference Encoder | 1,637,269 Bytes |
| Converter | 64,314,222 Bytes |
| 合計 | 65,951,491 Bytes |
合計サイズは約65.95 MB、バイナリ換算では約62.9 MiBです。
モデルは管理下にあるHugging FaceとModelScopeのリポジトリへ配置し、不変のリビジョンに固定しています。中国語・中国国内向けセッションではModelScopeを優先し、それ以外ではHugging Faceを優先します。片方の取得に失敗した場合は、もう一方へフォールバックします。
配信元が異なっても同一モデルとして扱えるように、プロバイダーに依存しない共通キャッシュIDを使用しています。
音声の前処理
アップロードまたは録音された音声はWeb Audio APIでデコードし、次の処理を行います。
- モノラル化
- 22,050 Hzへのリサンプリング
- Float32 PCMへの変換
- レベルの正規化
- 無音および低エネルギー部分の検出
OpenVoiceへ直接PCMを入力するのではなく、STFTによって周波数特徴量へ変換します。
Sample Rate: 22050 Hz
FFT Size: 1024
Hop Length: 256
Frequency: 513 bins
Reference Encoderの入力形状は次の通りです。
[1, frameCount, 513]
Converterへ入力するスペクトログラムは軸順を変更します。
[1, 513, frameCount]
変換時には、さらに以下のテンソルを使用します。
Frame Mask: [1, 1, T]
Source Embedding: [1, 256, 1]
Target Embedding: [1, 256, 1]
Noise: [1, 192, T]
ノイズはシード付きで生成し、同一入力に対する結果を再現しやすくしています。
WebGPUとWASMの実行経路
推論にはONNX Runtime Webを利用しています。
Reference Encoderは比較的小さいためWASMで実行し、ConverterはWebGPUを優先します。
Reference Encoder → WASM
Converter → WebGPU
↘ WASM fallback
初期化時の主な設定は以下です。
graphOptimizationLevel = "all"
WebGPU powerPreference = "high-performance"
WASM SIMD = true
WASM threads = 1〜4
WASMのマルチスレッドは、ページがcrossOriginIsolatedである場合のみ有効にしています。
WebGPU Sessionの作成に失敗した場合は、WASM専用Sessionを新たに作成します。WebGPUに対応していない環境でも、機能全体が即座に利用不能にならない構成です。
モデルファイルは並列ダウンロードしますが、ONNX Sessionは直列に初期化します。
モデル取得:並列
Session作成:直列
これによりダウンロード待ち時間を短縮しながら、複数Sessionの同時作成による瞬間的なメモリ増加を抑えています。
推論をWeb Workerへ分離
STFTとモデル推論をUIスレッドで実行すると、スクロールやタイムライン操作が停止する可能性があります。
そのため、推論処理全体をWeb Workerへ分離しました。
Main Thread
│
├─ PCM・設定・埋め込みを送信
│
Web Worker
├─ STFT
├─ Reference Encoder
├─ Converter
├─ 音声後処理
└─ 変換済みPCMを返却
PCMの受け渡しにはTransferableを使用し、大きなFloat32Arrayのコピーを避けています。
キャンセル時にはフラグを変更するだけでなく、実行中のWorkerを終了し、保留中のPromiseをrejectします。
変換後に発生する小さな長い余韻
実際の音声を検証すると、発話終了後に非常に小さい音が長く残るケースがありました。
これは、スペクトログラムの復元、パディング領域、モデル出力に残る低エネルギー成分などが影響します。
固定秒数で単純に切り取ると自然な語尾まで失われるため、RMSを利用して最後の有効な音声位置を検出しています。
RMS Window: 20 ms
RMS Hop: 10 ms
Minimum Peak: 0.0025
Activity Threshold: max(0.0015, peakRms × 0.035)
Tail Padding: 160 ms
Fade Out: 40 ms
最後の有効区間から160 msを残し、末尾40 msへコサインフェードを適用します。自然な語尾を維持しつつ、数秒間続く小さな残響を削減できます。
クローン音声の音量調整
声質変換後の音声は、元のTTSより少し小さくなる場合があります。
エディターでは音量を0〜400%まで調整可能にしました。ただし、単純な線形増幅だけではクリッピングが発生します。
そこでtanhベースのソフトリミッターを使用しています。
Volume: 0〜400%
Limiter: tanh
Drive: 1.35
音量設定は試聴だけでなく、タイムラインのプレビューと最終エクスポートにも反映されます。
IndexedDBによる声質の保存
参照音声、テスト結果、話者埋め込みはIndexedDBへ保存します。
一つの音声プロファイルには次の情報を保持できます。
id
name
referenceBlob
testBlob
speakerEmbedding
language
favorite
authorization
createdAt
updatedAt
ユーザーは参照音声のアップロード、ブラウザからの録音、クローン結果の試聴、声質の保存、お気に入り登録と解除、過去の声質の再利用、ローカルプロファイルの削除を行えます。
話者埋め込みも保存するため、同じ声質を使用するたびにReference Encoderを再実行する必要はありません。
モデルキャッシュと容量不足
モデルはバージョン付きのCache Storageへ保存します。
ただし、ブラウザや端末によっては約66 MBのモデルでもQuotaExceededErrorが発生します。キャッシュ失敗を推論失敗として扱うと、モデルのダウンロードが完了していても機能を利用できません。
モデルをダウンロード
↓
Cache Storageへの保存を試行
├─ 成功:次回から再利用
└─ 失敗:メモリ上のモデルで推論を続行
可能な場合はPersistent Storageも要求します。保存容量が不足している場合、ユーザーには生の例外ではなく、メモリ実行へ切り替えたことを通知します。
FP16を採用し、FP8を製品版に入れなかった理由
現在の製品版はFP16で固定しています。
ONNX自体はE4M3やE5M2などのFloat8型を表現できます。しかし、ブラウザのWebGPU/WGSL環境には、一般的かつ安定したネイティブFP8実行経路がまだありません。
FP8モデルを配置できたとしても、実行時に一部のノードをFP16またはFP32へ変換する可能性があります。その場合、次の検証が必要になります。
- CastおよびDequantizeノードの追加
- 未対応演算子のフォールバック
- CPUとGPU間の転送
- ブラウザおよびGPUごとの互換性
- 声質、ノイズ、語尾への影響
- 新しいモデルとキャッシュIDの管理
現在の判断は次の通りです。
製品版:FP16
FP8:独立した実験として管理
モデルファイルのサイズだけでなく、ブラウザ上で実際にどの精度で演算されるか、グラフがどこへフォールバックするかまで確認できてから導入する方針です。
タイムラインとの接続
変換が完了しても、現在のクリップは自動的に置換しません。
ユーザーは結果を試聴した後、My assetsへの保存、再変換、現在のクリップの置換、元の音声への復元、音声ファイルのダウンロードを明示的に選択できます。
現在のクリップを置換する場合は、タイムライン上の位置と編集コンテキストを維持し、音声アセットと長さだけを更新します。試験的な変換によって編集内容が意図せず上書きされないようにするためです。
実装後の処理構成
最終的なブラウザ内処理は次のようになりました。
多言語TTS
→ 音声デコード
→ 22,050 Hzへのリサンプリング
→ STFT
→ 話者特徴量の抽出
→ OpenVoice V2による声質変換
→ 末尾の低エネルギー成分を整理
→ 音量補正
→ IndexedDBへ声質を保存
→ タイムラインで試聴・置換
→ ローカルエクスポート
モデルをONNXへ変換してブラウザで実行するだけでは、実際の編集機能としては不十分でした。
モデル配信、実行バックエンド、Worker間転送、キャッシュ容量不足、音声の長い余韻、音量補正、声質の再利用、タイムライン上の安全な置換までを一つのワークフローとして接続することで、継続的に利用できる機能になりました。
実装全体と更新履歴はGitHubリポジトリで公開しています。