UnityのMicrophoneで正確な録音時間を取得する方法

# はじめに
スマホのマイクを使って音声を録音する仕組みをUnityで作ることになりました。
ボタンをタップしたら録音開始して、もう一度ボタンをタップしたら録音を停止するということをやれればOKなのですが、正確な録音時間を取得できないという問題が発生しました。その解決方法を紹介します。

# UnityのMicrophone
UnityにはMicrophoneというクラスが標準で備わっており、これを使えば録音は簡単にできます。
[Unity公式ドキュメント｜Microphone](https://docs.unity3d.com/ja/540/ScriptReference/Microphone.html)

iPhone/Android/Macの場合、標準のマイクで録音できます。
Windowsの場合、マイクが繋がっていれば、Unityがいい感じに見つけてくれます。（たぶん）

# サンプルコード（追記: 2018/10/21）
サンプルのUnityプロジェクトを作りました。ご自由にご利用ください。
https://github.com/segurvita/UnityMicrophonePractice

ボタン操作で、録音の開始・終了と再生ができるようにしてあります。

# 解説
サンプルコードの中身はこんな感じです。

```C#
using UnityEngine;

public class RecordManager : MonoBehaviour
{
    //公開変数
    public int maxDuration;                  //最大の録音時間（20秒とか）
    public AudioClip audioClip;              //音声データ

    //非公開変数
    private const int sampleRate = 16000;    //録音のサンプリングレート
    private string mic;                      //マイクのデバイス名

    ///-----------------------------------------------------------
    /// <summary>録音開始</summary>
    ///-----------------------------------------------------------
    public void StartRecord()
    {
        //マイク存在確認
        if (Microphone.devices.Length == 0)
        {
            Debug.Log("マイクが見つかりません");
            return;
        }

        //マイク名
        mic = Microphone.devices[0];

        //録音開始。audioClipに音声データを格納。
        audioClip = Microphone.Start(mic, false, maxDuration, sampleRate);
    }

    ///-----------------------------------------------------------
    /// <summary>録音終了</summary>
    ///-----------------------------------------------------------
    public void StopRecord()
    {
        //マイクの録音位置を取得
        int position = Microphone.GetPosition(mic);

        //マイクの録音を強制的に終了
        Microphone.End(mic);

        //再生時間を確認すると、停止した時間に関わらず、maxDurationの値になっている。これは無音を含んでいる？
        Debug.Log("修正前の録音時間: " + audioClip.length);

        //音声データ一時退避用の領域を確保し、audioClipからのデータを格納
        float[] soundData = new float[audioClip.samples * audioClip.channels];
        audioClip.GetData(soundData, 0);

        //新しい音声データ領域を確保し、positonの分だけ格納できるサイズにする。
        float[] newData = new float[position * audioClip.channels];

        //positionの分だけデータをコピー
        for (int i = 0; i < newData.Length; i++)
        {
            newData[i] = soundData[i];
        }

        //新しいAudioClipのインスタンスを生成し、音声データをセット
        AudioClip newClip = AudioClip.Create(audioClip.name, position, audioClip.channels, audioClip.frequency, false);
        newClip.SetData(newData, 0);

        //audioClipを新しいものに差し替え
        AudioClip.Destroy(audioClip);
        audioClip = newClip;

        //再生時間
        Debug.Log("修正後の録音時間: " + newClip.length);
    }
}
```

# StartRecordの解説
録音開始は簡単で

```C#
audioClip = Microphone.Start(mic, false, maxDuration, sampleRate);
```
を呼ぶだけです。
特に録音終了処理をしなければ、maxDurationで指定した秒数だけ録音します。
録音した音声データはaudioClipに格納されます。このaudioClipというのは、AudioClipクラスの変数です。AudioClipは、Unity内で音データを扱うためのクラスです。
録音した音声をスピーカーで再生したければ、audioClipをAudioSourceにぶち込んでPlay()すれば良いです。

# StopRecordの解説
録音を途中で終了させたい場合、色々と面倒なことをする必要があります。

```C#
Microphone.End(mic);
```
を呼べば、録音を停止することはできるのですが、音声データを格納しているaudioClipが変な状態になります。
3秒くらいで録音を停止したはずなのに、 `audioClip.length` （録音時間）が、20秒となっていました。

どうやら、`Microphone.Start()`を呼んだ時点で、 `audioClip.length` は `maxDuration` の数値に固定されるようです。
これでは後処理（例えば、WAVファイルに変換する等）で困るので、正確な再生時間に修正する必要があります。

`audioClip.length` が読み込み専用で書き換えることができなかったので、audioClipのインスタンスを再構築するという方法で解決しました。この方法はこちらのQ&Aを参考にさせていただきました。
[record dynamic length from microphone](https://answers.unity.com/questions/544264/record-dynamic-length-from-microphone.html)

もっと良い方法があれば、教えてください。

また、途中で

```C#
int position = Microphone.GetPosition(mic);
```
を呼んで、録音位置 `position` を取得しています。
~~録音時間が短すぎると `position==0` となってしまうことが頻発したので、録音時間が最小値 `minDuration` よりも小さい場合は、数値を強制代入しています。
この場合、足りない音声データは無音が追加されているようです。~~

2018/08/20 追記
コメントでご指摘いただき、この問題は解決しました。（ご指摘ありがとうございます！）

もともと掲載していたサンプルコードでは、 `Microphone.End(mic);` よりも後に `int position = Microphone.GetPosition(mic);` を実行していましたが、この場合、`position`の値は`0`となってしまうようです。

`Microphone.End(mic);` よりも前に `int position = Microphone.GetPosition(mic);` を実行したところ、正常な `position` の値を取得することができると、ご報告いただきましたので、確認したところ、確かに正常な値を取得することができました。

このことを踏まえて、サンプルコードの内容は修正いたしました。


# まとめ
UnityのMicrophoneクラスを使えば、簡単にマイク録音が実現できますが、正確な録音時間を取得するには色々と処理が必要です。