OpenSiv3Dでwhisper.cppを使ってみる

## はじめに

この記事は[前回の記事](https://qiita.com/23tas9/items/7d21252b975e9a1ded8c)の続きの記事です
前回の記事ではwhisper.cppのGPU付きでのビルドをしました。

## whisper.cppの静的ライブラリを作成する

whisper.cppの静的ライブラリを作成するために、以下のcmakeコマンドを実行します。

```terminal
cmake -S . -B ./build -A x64 -DCMAKE_BUILD_TYPE=Release -DGGML_CUDA=ON -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF
```

今回は静的ライブラリを使っていきたいので、上記のようなコマンドを実行しました。

[参考](https://github.com/ggerganov/whisper.cpp/issues/212)

https://github.com/ggerganov/whisper.cpp/issues/212

また、静的ライブラリを生成するために、ランタイムライブラリを変更しなければなりません。

### ビルドの準備
ソリューションエクスプローラーから
`bench`, `common`, `ggml`, `ggml-base`, `ggml-cpu`, `main`, `quantize`, `server`, `whisper`を選択し、

![whisper.cpp ソリューションエクスプローラー画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/9542d309-cc3b-1d1a-48a2-e5b9c8b5a077.png)

プロパティから`構成プロパティ > C/C++ > コード生成 > ランタイムライブラリ`を
`Release`構成では`マルチスレッドDLL(/MD)`から`マルチスレッド(/MT)`に
`Debug`構成では`マルチスレッドデバッグDLL(/MDd)`から`マルチスレッドデバッグ(/MTd)`に変更します。

次に、`ggml-cuda`のプロパティから
`構成プロパティ > CUDA C/C++ > Host > Runtime Library`を上記と同じように、
`Release`構成では`マルチスレッドDLL(/MD)`から`マルチスレッド(/MT)`に
`Debug`構成では`マルチスレッドデバッグDLL(/MDd)`から`マルチスレッドデバッグ(/MTd)`に変更します。

それぞれ済んだら、ウィンドウ上部のメニューバーから`ビルド > バッチビルド`を選択します。その後、`ALL_BUILD`の`Debug`と`Release`それぞれのビルドにチェックを入れ、ビルドをします。

![バッチビルド](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703604/69893a33-5725-8496-e4a8-1d869a7a6273.png)

少し時間がかかりますが、気長に待ちましょう。

## OpenSiv3Dプロジェクトを作成する

[公式サイト](https://siv3d.github.io/ja-jp/#siv3d-%E3%82%92%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89%E3%81%99%E3%82%8B)から、手順通りにプロジェクトを作成してください。

### インクルードディレクトリの追加

まず、構成が`すべての構成`になっていることを確認します。

次に、プロジェクトのプロパティから
`構成プロパティ > C/C++ > 全般 > 追加のインクルードディレクトリ`
に以下を追加します

```
whisper.cpp/include
whisper.cpp/ggml/include
```

### ライブラリディレクトリの追加

構成を`Debug`に変更し
`構成プロパティ > リンカー > 全般 > 追加のライブラリディレクトリ`
に以下を追加します。

```
whisper.cpp/build/src/
whisper.cpp/build/ggml/src/
```

### 依存ファイルの追加

構成を`Debug`のままで、
`構成プロパティ > リンカー > 入力 > 追加の依存ファイル`
に以下を追加します

```
Debug/whisper.lib
Debug/ggml.lib
Debug/ggml-base.lib
Debug/ggml-cpu.lib
ggml-cuda/Debug/ggml-cuda.lib
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\lib\x64\cudart_static.lib
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\lib\x64\cublas.lib
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\lib\x64\cublasLt.lib
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\lib\x64\cuda.lib
```

また、構成を`Release`に変更し、同じく
`構成プロパティ > リンカー > 入力 > 追加の依存ファイル`
に以下を追加します

```
Release/whisper.lib
Release/ggml.lib
Release/ggml-base.lib
Release/ggml-cpu.lib
ggml-cuda/Release/ggml-cuda.lib
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\lib\x64\cudart_static.lib
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\lib\x64\cublas.lib
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\lib\x64\cublasLt.lib
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\lib\x64\cuda.lib
```

## OpenSiv3Dでwhisper.cppを実行する
以下のコードを実行し、シーン真ん中のボタンを押すとコンソールに実行結果が表示されます！

```cpp:main.cpp
#include <Siv3D.hpp> // Siv3D v0.6.15
#include <whisper.h>

String getTimestampString(const int64& t) {
	const Milliseconds ms{ t * 10 };

	return U"{:0>2d}:{:0>2d}:{:0>2d}.{:3d}"_fmt(
		DurationCast<Hours>(ms).count() % 24,
		DurationCast<Minutes>(ms).count() % 60,
		DurationCast<Seconds>(ms).count() % 60,
		ms.count() % 1000
	);
}

void Main() {
	// モデルのパス
	const FilePath modelPath = UR"(C:\Users\tas9n\Desktop\whisper_test\ggml-base.en.bin)";

	// 音声ファイルのパス
	const FilePath audioPath = UR"(C:\Users\tas9n\Desktop\whisper_test\test.wav)";

	whisper_context_params params = whisper_context_default_params();

	// コンテキストの作成
	whisper_context* ctx = whisper_init_from_file_with_params(modelPath.narrow().c_str(), params);

	if (ctx == nullptr) {
		whisper_free(ctx);

		throw Error{ U"コンテキストの作成に失敗しました。モデルパスなどを確認してください。" };
	}

	Wave wav{ audioPath };

	if (wav.isEmpty()) {
		whisper_free(ctx);

		throw Error{ U"音声ファイルが無効です。" };
	}

	wav.setSampleRate(16000);

	Array<float> samples;

	for (auto& sample : wav) {
		samples << (sample.right + sample.left) / 2;
	}

	// 推論
	whisper_full_params fullParams = whisper_full_default_params(WHISPER_SAMPLING_GREEDY);
	fullParams.language = "en";

	while (System::Update()) {
		if (SimpleGUI::ButtonAt(U"実行", Scene::CenterF())) {
			if (whisper_full(ctx, fullParams, samples.data(), samples.size()) != 0) {
				Print << U"失敗";
				continue;
			}

			// データの取得
			const int32 segmentCount = whisper_full_n_segments(ctx);

			for (int32 i : step(segmentCount)) {
				const int64 from = whisper_full_get_segment_t0(ctx, i);
				const int64 to = whisper_full_get_segment_t1(ctx, i);

				const char* rawText = whisper_full_get_segment_text(ctx, i);

				const String text = Unicode::FromUTF8(rawText);

				Console << U"[{} --> {}]: {}"_fmt(getTimestampString(from), getTimestampString(to), text);
			}
		}
	}

	whisper_free(ctx);
}
```

## まとめ
今回はOpenSiv3Dでwhisper.cppを使ってみました。
2年前に[Bulletを使った](https://qiita.com/23tas9/items/522312d04013f3512f33)のが今回のビルドの際に役立ちましたのでよかったらこっちの記事も読んでください😊

また、前回の記事では

> 動画編集などの助けになればいいと考え、字幕生成ツールを作ろうとしたといった背景があります。

といっていましたので、次回は字幕生成ツールを作成しようかなと思います。