HoloLensでTranslator Speech APIを使ったリアルタイム翻訳-その2リアルタイム音声入力

# マイクの音声をリアルタイムに読み取る
[前回](http://qiita.com/miyaura/items/febee8ca6f9050c9ff26)作ったライブラリを使うことで、翻訳したデータを取得することは可能になります。あと必要なものとして、翻訳したいデータを音声としてサービスへ送るためにリアルタイムに音声データをサンプリングしていく必要があります。色々方法を調べるとUnityだけでできる方法とHoloToolkitを組み合わせる方法がありましたので、紹介します。試した限りはUnityだけの方が安定した品質のデータをとれている気がします。

## 今回の環境
環境については以下の通りです。

* IDE
  * Unity 5.5.0f3
  * Visual Studio 2015 update 3 Community Edition
* ライブラリ関連
  * HoloToolkit-Unity 1.5.5.0

## サンプルコード
サンプルコートは以下の場所にgithubに公開しています。UnityプロジェクトはHoloLens用にビルドできる形にしています。リアルタイム音声入力についてはUnityだけ、HoloToolkitを使う方法両方を含めています。

https://github.com/TakahiroMiyaura/HoloTranslatorSpeechSamples/tree/master/MicrophoneStreamingSamples

## 実装方法

Unityのみ、HoloToolkitを組み合わせる方法両方でのやり方について紹介します。

### Unityだけで実施する方法
Unityだけで実現する場合はAudioSourceとMicrophoneを利用します。

#### マイクの音声入力準備と開始
AudioSourceの再生対象としてclipにマイクのソースを設定します。その後マイクの初期化を待ってAudioSourceを再生します。サンプリングレートはAudioSettings.outputSampleRateの値を設定します。

```csharp:MicStreaming.cs
// Copyright(c) 2017 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php

//set microphone.
audio = GetComponent<AudioSource>();
audio.clip = Microphone.Start(deviceName, false, 999, AudioSettings.outputSampleRate);
audio.loop = true;
while (!(Microphone.GetPosition(deviceName) > 0)) { }

//recording start.
audio.Play();

```

#### マイクの音声入力の停止と終了
停止についてはAudioSourceの停止とマイクの終了を実行します。

```csharp:MicStreaming.cs
audio.Stop();
Microphone.End(deviceName);
```

#### マイクの音声入力情報の取得
マイクからの音声情報の取得についてはMonoBehaviour.OnAudioFilterReadメソッドで可能です。このメソッドは定期的に（～20ms）呼び出され、その時のサンプリングデータを取得することが可能です。引数としてサンプリングデータとチャネル数が返ります。サンプリングデータは-1.0f～1.0fの配列となります。このfloat値に量子化ビットに応じた値を掛けることで、Waveデータの情報を生成します。サンプルでは量子化ビットを16bitで処理するためにShortに変換してます。変換は単純にMAX値で掛け算するだけです。なお、チャネル数が2で返ってきている場合は、bufferの配列情報は添え字の奇数と偶数で右音声、左音声が入っています。

```csharp
// Copyright(c) 2017 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php

private void OnAudioFilterRead(float[] buffer, int numChannels)
{
    if (!_isStart) return;
        
    lock (this)
    {
        foreach (var f in buffer)
        {
            samplingData.Add(FloatToInt16(f));
        }
    }
}

/// <summary>
///     The bytes that we get from audiograph is in IEEE float, 
/// </summary>
/// <param name="value">sampling Data</param>
/// <returns>waveform data(16bit)</returns>
private static short FloatToInt16(float value)
{
    var f = value * short.MaxValue;
    if (f > short.MaxValue) f = short.MaxValue;
    if (f < short.MinValue) f = short.MinValue;
    return (short) f;
}
```

サンプルではリアルタイムで収集した音声データをいったんバッファリングし停止時にファイルに出力しています。実際の処理ではOnAudioFilterReadメソッドの処理でリアルタイムに何かをするという実装になると思います。ただし、OnAudioFilterReadメソッドは通常のUnityのメインスレッドとは異なるスレッドで駆動するため、直接GameObjectなどを操作することはできません。いったんフィールドに値を設定し、Updateメソッド側で処理させる必要があります。

### HoloToolkitを利用する方法
HoloToolkitを利用する場合はAudioSourceとMicStreamを利用します。
MicStreamというのがHoloLensで音声系の機能を利用する際のライブラリになります。
上記のUnityのみと同じことを実施する際の実装方法を以下に紹介します。なお、MicStreamの使い方についてはサンプルコートがHoloToolkitのInput\Tests\Scrpits\MicStreamDemo.csにあります。Cubeなどの3DオブジェクトにAudioSourceとMicStreamDemoコンポーネントを追加すると、マイクの音声でCubeの大きさが変わるのを確認することができます。

#### マイクの音声入力準備と開始
音声入力の準備についてはMicStream.MicInitializeCustomRateメソッドで初期化を行い、 MicStream.MicStartStreamメソッドで開始します。MicInitializeCustomRateメソッドは音声の品質を設定します。品質は引数で設定し、品質のレベル（High,Low,Room）とサンプリングレートを指定します。
MicSetGainはいわゆるボリュームに相当します。マイクの音声が小さいときにこの値を
大きくします。デフォルトは１です。

```csharp:MicStreamingForHoloToolKit.cs
// Copyright(c) 2017 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php

private void Awake()
{
    CheckForErrorOnCall(MicStream.MicInitializeCustomRate((int)StreamType, AudioSettings.outputSampleRate));
}

void Update ()
{
    if (Input.GetKeyDown(KeyCode.W))
    {
        samplingData.Clear();
    CheckForErrorOnCall(MicStream.MicStartStream(KeepAllData, false));
    CheckForErrorOnCall(MicStream.MicSetGain(InputGain));

        _isStart = true;
    }
    else if (Input.GetKeyDown(KeyCode.S))
    {
        _isStart = false;
        CheckForErrorOnCall(MicStream.MicStopStream());
        WriteAudioData();

    }
}
```

#### マイクの音声入力の停止と終了
停止についてはMicStream.MicStopStream()メソッドを呼び出します。

```csharp:MicStreaming.cs
CheckForErrorOnCall(MicStream.MicStopStream());
```

#### マイクの音声入力情報の取得
マイクからの音声情報の取得についてはUnityでの方法と同様にMonoBehaviour.OnAudioFilterReadメソッドで行います。ただし、このメソッドの引数のBufferにはデータが入っていません。データについてはMicStream.MicGetFrame
メソッドを呼び出して値を取得します。それ以外の考え方はUnityの方法と同様です。

```csharp:MicStreaming.cs
// Copyright(c) 2017 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php

private void OnAudioFilterRead(float[] buffer, int numChannels)
{
    if (!_isStart) return;
        
    lock (this)
    {
        CheckForErrorOnCall(MicStream.MicGetFrame(buffer, buffer.Length, numChannels));
        foreach (var f in buffer)
        {
            samplingData.Add(FloatToInt16(f));
        }
    }
}

/// <summary>
///     The bytes that we get from audiograph is in IEEE float, 
/// </summary>
/// <param name="value">sampling Data</param>
/// <returns>waveform data(16bit)</returns>
private static short FloatToInt16(float value)
{
    var f = value * short.MaxValue;
    if (f > short.MaxValue) f = short.MaxValue;
    if (f < short.MinValue) f = short.MinValue;
    return (short) f;
}
```

## 最後に
思ったよりも容易にリアルタイムで音声入力のデータをとることができました。サンプルは音声の品質はかなり高めなので割と再現性が高いです。HoloLensでもリアルタイムに音声入力できるといろいろと面白いことができると思います。
ここまでくれば、HoloLensでリアルタイム翻訳まであと一歩です。