Unity + ADX環境でゲーム中の音を録音する


## はじめに

ゲームの機能として、アプリ内でゲームの様子をキャプチャしてウェブ投稿したり、なんらかの音の組み合わせをアプリで作ってwavに出力したい場合などがあります。いわゆるUser Generated Contentsの機能です。

ADXには標準ではアプリ内でゲームの音を録音する機能がありませんが、PCMデータを取得するクラスがありますので、これを利用したパッケージADX2 for Unity Recorderを作成しました。本記事ではその内容と使い方を解説します。

アプリ上だけではなく、トレイラームービー用などでUnity Editor内でADXの音をキャプチャしたい場合にも同じ仕組みが転用できます。詳しくは次の記事をご確認下さい。

https://qiita.com/Takaaki_Ichijo/items/853d963b3341b54d6454

## 動作確認環境

Windows 10 Home 20H2
Unity 2020.3.27f1  + ADX LE Unity SDK 3.06.03
CRI ADX LE Tools for Windows 3.46.02

## ADX2 for Unity Recorder

https://github.com/TakaakiIchijo/ADX2forUnityRecorder

※ADX2はADXの旧名称です。動作には影響ありません。

パッケージには３つのスクリプトファイルが含まれています。CriAtomExOutputAnalyzerで音をキャプチャするCriAtomRecorder.csと、WAVEフォーマットを作ってファイルを生成するWaveFileCreator.cs、Waveファイルの出力先パスを作るRecordingADX2OnRuntime.csです。

### 処理概要

ADXには、様々なエフェクト処理やミキシングを行ったあとの最終出力音声に対して、PCMの生データ（バイト配列）で取得できる仕組みがあります。それがCriAtomExOutputAnalyzerクラスのExecutePcmCaptureCallbackです。

[https://game.criware.jp/manual/unity_plugin/jpn/contents/classCriAtomExOutputAnalyzer.html](https://game.criware.jp/manual/unity_plugin/jpn/contents/classCriAtomExOutputAnalyzer.html)

これを使って、実行中の音を録音し、waveファイルとして保存します。

### 取得した生データをwave形式のフォーマットに変換

ExecutePcmCaptureCallbackによってPCMデータをbyte配列として取得できますが、このままバイナリに書き出してもPCなどで再生できるデータになりません。
WAVEファイル形式に沿ったデータを作り、フォーマットに沿ってデータを保存してあげる必要があります。
WaveFileCreator.csは、WAVEファイルの生成と録音データの詰め込みを行うスクリプトです。CriAtomRecorder.csはこれを使ってキャプチャしたデータを保存します。

## 実装方法

ADXを導入しているプロジェクトへ上記パッケージをインポートします。
Unity Recoderを前提としているため、入れていない場合はPackage Managerからインストールします。

![Unity Recoderのインストール](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/32691890-1e18-9d42-5e3b-fa27919c5779.png)


その上で、CriAtomRecorderInstance.csをアタッチしたゲームオブジェクトをシーンに設置します。また、RecordingADX2OnRuntime.csをアタッチし、Atom RecorderプロパティにCriAtomRecorderInstance.csへの参照を入れます。

![CriAtomRecorderの配置](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/ac594c8e-54e4-42ec-ce6e-a4a429fed8d6.png)

RecordingADX2OnRuntimeのOnRecordStart、OnRecordStopメソッドをButtonなどで呼び出すことにより、録音することができます。

### 生成されたWAVEデータの保存先

RecordingADX2OnRuntime.csのデフォルト設定では、ファイルは次のフォルダに保存されます。

> PC: C:\Users[UserName]\AppData\LocalLow[OrganizationName][AppName]\SavedGameData
iOS: Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Documents\SavedGameData
Android: /data/[PackageName]/files\SavedGameData
> 

このファイルを内部ストレージに保存して、外のサーバーにアップロードしたり、アプリ内だけで再生できるコンテンツとして保存することもできます。

なお、iOSの場合はデフォルトではiCloudに同期されるデータとして保存されます。iCloudバックアップにファイルを含めたくない場合は、SetNoBackupFlagをパスに指定します。

```NoiCloudBackup.cs
#if UNITY_IOS
UnityEngine.iOS.Device.SetNoBackupFlag(path);
#endif
```

## 処理詳細

ここからはパッケージの録音機能本体であるCriAtomRecorderInstance.csの解説になります。

録音前の準備として、設定を行うためSetParametersメソッドがあります。

```CriAtomRecorderInstance.cs
    public void SetParameters(string filePath, int outPutSamplingRate = 0)
    {
        if (outPutSamplingRate == 0)
        {
            outPutSamplingRate = 48000;
        }

        waveFileCreator = new WaveFileCreator(
            filePath,
            numChannels:2,
            outPutSamplingRate,
            numbites:16
        );
        IsRecording = false;
    }
```

ここでチャンネル数やサンプリングレート、保存ファイルのパスの設定を行います。wavファイルの再生速度がゆっくりだったり早かったりした場合は、出力サンプリングレートが間違っている可能性があります。

StartRecordingCoroutineメソッドで録音を開始します。

```CriAtomRecorderInstance.cs
		public void StartRecordingCoroutine()
    {
        recordingCoroutine = RecordCoroutine();
        
        StartCoroutine(recordingCoroutine);
    }

    IEnumerator RecordCoroutine()
    {
        while (CriAtom.CueSheetsAreLoading) {
            yield return null;
        }

        /* Initialize CriAtomExOutputAnalyzer for PCM capture. */
        CriAtomExOutputAnalyzer.Config config = new CriAtomExOutputAnalyzer.Config
        {
            enablePcmCapture = true, 
            enablePcmCaptureCallback = true, 
            numCapturedPcmSamples = numSamples
        };
        
        analyzer = new CriAtomExOutputAnalyzer(config);
        analyzer.SetPcmCaptureCallback(PcmCapture);
        
        analyzer.AttachDspBus("MasterOut");

        IsRecording = true;
    }

```

一旦ACBファイルのロードを待ったあと、CriAtomExOutputAnalyzerクラスのインスタンスを生成し、各種設定を詰めたコンフィグファイルを渡します。
生成されたクラスに対して、録音対象のDSP Busを指定します。すべての音を録音する場合は"MasterOut"を指定します。
また、コールバックメソッドにPcmCaptureメソッドを指定します。

PcmCaptureメソッドは、先程生成したwaveFileCreatorクラスに対してキャプチャしたPCM生データをひたすら渡すメソッドです。

```CriAtomRecorderInstance.cs
		private void PcmCapture(float[] dataL, float[] dataR, int numChannels, int numData)
    {
        if (!IsRecording || waveFileCreator == null)
            return;
        
        waveFileCreator.CapturePcm(dataL, dataR, numData);
    }

```

CriAtomExOutputAnalyzerは毎フレームExecutePcmCaptureCallbackを呼び出す必要がありますので、録音開始されていたらUpdateで呼びます。

```CriAtomRecorderInstance.cs
		private void Update()
    {
        if (IsRecording)
        {
            analyzer.ExecutePcmCaptureCallback();
        }
    }

```

最後に録音停止のメソッドです。まず開始したコルーチンを止めます。
また、このゲームオブジェクトが破棄されたときにwaveファイルが正しくクローズ処理されるように、CriAtomExOutputAnalyzerクラスがちゃんと破棄されるようにOnDisableメソッドがあります。

```CriAtomRecorderInstance.cs
		public void StopRecording()
    {
        if (IsRecording == false) return;

        StopAndWrite();
        IsRecording = false;
        StopCoroutine(recordingCoroutine);
        recordingCoroutine = null;
    }

    private void StopAndWrite()
    {
        if (waveFileCreator == null || IsRecording == false) return;

        waveFileCreator.StopAndWrite();

        if (analyzer != null) {
            analyzer.DetachDspBus();
            analyzer.Dispose();
        }
    }

    private void OnDisable()
    {
        StopAndWrite();
    }

```