【Unity】NAudioで音声出力デバイスを指定して音声を再生する

# ■はじめに
**※はじめに、あんまり為にならないマニアックな知識を紹介しています。**

理由としては、言わずもがなUnityでの音声再生はAudioSourceを利用した方が楽だからです。

僕は、**「音声出力デバイスを指定して、音声再生をしたい」** という理由でNAudioを利用しました。
細かいことは諸事情によりお伝えできませんが、音声を利用して特殊な処理をするためにこのような方法をとっています。

また、調べてみると、マイク入力や音声ファイルの変換などにNAudioをUnity上で扱っている方がいました。

https://qiita.com/toqkz/items/142c79aaa1d831cb5a52

https://www.hanachiru-blog.com/entry/2022/09/19/120000

たぶん使う人は少ないと思いますが、個人的な忘備録として残しておきます。


# ■実行結果
音声を利用する処理なので、視覚的にお伝えできませんが、
例えば、「**BGMをスピーカー1でAudioSourceを利用して再生しながら、SEをスピーカー2でNAudioを利用して再生する**」というようなことが実装できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2419795/e5adc701-5351-7007-c9f5-42dd0cb28475.png)

僕はこんな感じで利用しました。

## ●Inspector
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2419795/eb411a46-abe9-3fdf-7929-3d3be1f80892.png)


上から順に、
- デバイス名のリスト
- 目標デバイスの要素番号
- 音声ファイルが存在するディレクトリ
を指定します。


# ■注意点
注意点として、今回紹介するコードは、音声のファイル形式は.wavでのみ対応しています。
.mp3のファイルを指定して再生しようとすると、エラーが発生します。

**mp3を再生させる場合、`WaveFileReader`クラスを`MP3FileReader`クラスに変更する必要があります。**

# ■セットアップ
NAudioを追加する方法として、VisualStudio内の Nugetを利用する方法と、Nugetのサイト上で直接全て揃える方法がありますが、当記事ではNugetを利用した方法を記載します。


1. VisualStudioのツールバーの「**プロジェクト / NuGetパッケージの管理**」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2419795/bd5977c0-3267-7f28-9e4f-57cdba80c133.png)
1. Nugetタブが追加されるので、その中の「**参照**」を選択、「**Nuget**」と検索して、一番上のを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2419795/de6f7423-6d98-de85-a7f6-c12fc5a214dc.png)
1. バージョンを **`1.8.0`** に変更して、「**インストール**」をクリック
**※Nugetで追加する場合、1.8.0じゃないとUnityでは動作しません。**
最新版を利用したい場合は、Nuget.org上で手動で全て揃えてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2419795/0bb9d0d4-539d-f1b7-4e7d-788dd8cde61c.png)

<br>

**Nugetでの追加はこれでOKですが、Unityのディレクトリの`Assets/Plugins`内に別途で.dllファイルを追加する必要があります。**

1. さっきの画面の右上のリンクをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2419795/9a5ba025-6bee-160c-1b97-b2e7cdc52e58.png)

1. 「**Versions**」を選択して、「**1.8.0**」を選択
1. 右側の「**Download package**」をクリックする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2419795/91d08c71-1c89-2674-b26b-5baebb889e19.png)

1. ダウンロードしたものを解凍して、**`NAudio.dll`をUnityプロジェクト内の`Assets/Plugins`に追加**


これで完了です。
(**目的のクラスが利用できない場合などは、VisualStudioを再起動させれば反映されます。**)



# ■コード
```NAudioTest.cs
using NAudio.Wave;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NAudioTest_2 : MonoBehaviour
{
    [Header("Device")]
    [SerializeField] List<string> deviceNames = new List<string>();         // 汎用デバイス名リスト
    [SerializeField] int targetDeviceNameIndex;                             // デバイス名リストの使用インデックス

    Dictionary<string,int> deviceDict = new Dictionary<string,int>();       // デバイスコレクション(key:デバイス名, value:デバイス番号)

    [Header("Directory")]
    [SerializeField] string audDirPath= "\\Assets\\Audio";                  // 音声ディレクトリ

    [Header("Audio")]
    Dictionary<string, AudioUnit> audioDict = new Dictionary<string, AudioUnit>();      // 音声コレクション

    /* Properties */
    /// <summary> 音声コレクション </summary>
    public Dictionary<string, AudioUnit> AudioDictionary => audioDict;

    //--------------------------------------------------
    // 音声情報クラス
    public class AudioUnit
    {
        string audioFileName;      // ファイル名
        string audioFilePath;      // ファイルパス

        WaveOutEvent waveOut = new WaveOutEvent();  // 音声データ
        WaveFileReader reader;

        //------------------------------
        // コンストラクタ
        public AudioUnit(string path, int deviceNum)
        {
            try {
                // ファイル情報セット
                audioFilePath = path;
                audioFileName = System.IO.Path.GetFileNameWithoutExtension(path);

				// WaveOutの初期化
                waveOut.DeviceNumber = deviceNum;
				reader = new WaveFileReader(audioFilePath);
				waveOut.Init(reader);
			}

            catch (System.Exception e) {
                Debug.LogWarning(e);
            }
		}

		// デストラクタ
		~AudioUnit()
		{
			waveOut?.Dispose();
		}

		//------------------------------
        /// <summary> 音声を再生する </summary>
        public void PlayAudio()
        {
            reader.Position = 0;        // 再生位置リセット
            waveOut?.Play();

            print($"{audioFileName} was played");
        }
    }

	//--------------------------------------------------
	void Start()
    {
        var currentDirPath = System.IO.Directory.GetCurrentDirectory();

		SetAudioUnits(currentDirPath + audDirPath, deviceNames[targetDeviceNameIndex]);
	}

	private void Update()
	{
        // キー入力で再生
        if (Input.GetKeyDown(KeyCode.Space)) {
            audioDict["exp_wav"].PlayAudio();
		}
	}

	//--------------------------------------------------
	// 指定フォルダ内の音声ファイルのパスを取得する
	string[] GetAudioFilePaths(string dirPath)
    {
        return System.IO.Directory.GetFiles(dirPath, "*.wav", System.IO.SearchOption.AllDirectories);
    }

    // 音声データセット
    void SetAudioUnits(string dirPath, string deviceName)
    {
        // コレクションのリセット
        audioDict.Clear();
        deviceDict.Clear();

        // デバイス取得
        deviceDict = GetDeviceNames();

        // デバイスに含まれているか
        if (deviceDict.ContainsKey(deviceName)) {
            // ファイルの音声をインスタンス化、コレクションに追加
            foreach (var filePath in GetAudioFilePaths(dirPath)) {
                var audUnit = new AudioUnit(filePath, deviceDict[deviceName]);
                audioDict.Add(System.IO.Path.GetFileNameWithoutExtension(filePath), audUnit);
            }
        }

        else {
            throw new System.Exception("指定されたデバイスは存在しません");
        }
    }

	//--------------------------------------------------
	// デバイス名取得
	Dictionary<string,int> GetDeviceNames()
    {
        var devices = new Dictionary<string, int>();

        for (int i = 0; i < WaveOut.DeviceCount; i++) {
            var capabilities = WaveOut.GetCapabilities(i);

            // デバイス名の重複判定
            if (!devices.ContainsKey(capabilities.ProductName)) {
                devices.Add(capabilities.ProductName, i);

                print(capabilities.ProductName);
            }
        }

        return devices;
    }
}
```

## ●解説
NAudio特有の部分のみ簡単に説明します。
その他はコメント読んでください…！

### AudioUnit
:::note
**Point💡**
- **WaveOutクラスを利用すると、Unityがエラーで落ちます。**
- WaveFileReaderは、音声の制御をします。(waveout初期化時に必要)
- コンストラクタでInit()、デストラクタでDispose()を呼び出します。
- **連続で再生可能にするために、再生されたら再生位置を0に戻します。**

:::

# ■さいごに
マジで誰も使うことなさそうな処理を紹介しました。

僕自身もUnityでは今後一切使うことないと思います。たぶん。




## ●参考サイト
↓リポジトリ

https://github.com/naudio/NAudio

↓Nuget

https://www.nuget.org/packages/NAudio/1.8.0#readme-body-tab

↓わかりやすい解説サイト

https://so-zou.jp/software/tech/programming/c-sharp/media/audio/naudio/

その他

https://qiita.com/toqkz/items/142c79aaa1d831cb5a52

https://www.hanachiru-blog.com/entry/2022/09/19/120000