Raw Audio Data を使って自分の音声を可視化してみる


#概要

Agora VideoSDK には通話中にリアルタイムで映像・音声の生データを取得し、これらを編集して SDK に戻す機能が備わっています。
※ SDK のプラットフォームによっては一部機能制限があります。

これを活用することで、外部ツールを使った音声解析や、ビデオ映像にスタンプや高度な美顔フィルタを適用するといったインタラクティブコンテンツを提供することができるようになります。

今回は [Raw Audio Data](https://docs.agora.io/en/Interactive%20Broadcast/raw_data_audio_unity?platform=Unity) を使い、自分の音声を波形データとしてグラフ化する実装を試してみます。実装にあたり VideoSDK for Unity に含まれるサンプルに追加する形で進めていきます。まだサンプルを動かした事がない場合、先に [Agora.io VideoSDK for Unity クイックスタートガイド](https://qiita.com/v-cube/items/b043065c7504a372f2b2)をご覧いただく事をお勧めします。


#実装

1．AudioRawDataObserver を登録し、コールバックを設定する
今回は自身の音声データを使うので、複数あるコールバックのうち SetOnRecordAudioFrameCallback を設定します。

```csharp:TestHelloUnityVideo.cs
    public void setUpRawAudioData() {
        mAudioManager = AudioRawDataManager.GetInstance(mRtcEngine);

        // Registers the audio observer.
        mAudioManager.RegisterAudioRawDataObserver();

        // Listens for the OnRecordAudioFrameHandler delegate.
        mAudioManager.SetOnRecordAudioFrameCallback(OnRecordAudioFrameHandler);
    }
```

RegisterAudioRawDataObserver は JoinChannel 前にコールしておく必要があります。

```csharp:TestHelloUnityVideo.cs
    public void join(string channel)
    {
        ... 

        setUpRawAudioData();

        // join channel
        mRtcEngine.JoinChannel(channel, null, 0);

        ... 
    }
```

2．コールバックを実装する
コールバックを設定すると、定期的に AudioFrame が引数で渡されてきます。AudioFrame はサンプリングされた音声データを含むオブジェクトで、これを一時的にためておくキューをクラス内に用意しておきます。今回、キューに格納されたデータは描画のタイミングで 1 個ずつ順番に取り出す実装にしますが、万が一描画のタイミングが遅いなどしてデータが増えすぎてしまわないよう、キューに格納できる上限を設けておきます。

```csharp:TestHelloUnityVideo.cs
    public void OnRecordAudioFrameHandler(AudioFrame audioFrame) {  
        if(audioFrameQueue.Count <= 3) {
            audioFrameQueue.Enqueue(audioFrame);
        }
    }
```


3．オーディオデータ取得のインターフェースを実装する
今回、音声波形の描画は TestHome.cs に任せるため、AudioFrame オブジェクトを返すメソッドを用意します。

```csharp:TestHelloUnityVideo.cs
    public AudioFrame getAudioFrame() {
        return (audioFrameQueue.Count > 0) ? (AudioFrame)audioFrameQueue.Dequeue() : new AudioFrame();
    }
```




4．描画ロジックを実装する
LineRenderer を使って音声波形を描画します。先ずは、線の色・太さを設定します。

```csharp:TestHome.cs
    void Start()
    {
        ...

        LineRenderer lineRenderer = gameObject.AddComponent<LineRenderer>();
                
        float lineWidth = 0.1f;
        lineRenderer.startWidth = lineWidth;
        lineRenderer.endWidth = lineWidth;
        
        Color lineColor = new Color(0.9f, 0.9f, 0.5f);
        lineRenderer.startColor = lineColor;
        lineRenderer.endColor = lineColor;

        lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
    }
```

次に Update() メソッドに描画ロジックを追加します。AudioFrame オブジェクトを扱うので、次の一行をファイルに追加しておきます:

```csharp:TestHome.cs
using agora_gaming_rtc;
```

AudioFrame に含まれるサンプル数と同じサイズの Vector3 配列 (頂点リスト)を用意し、各頂点の座標を計算していきます。サンプル一つ一つは 16 bit PCM 方式で格納されているため、復号してから Y 座標を計算していきます。


```csharp:TestHome.cs
    void Update()
    {
        ...

        if(app != null) {
            AudioFrame audioFrame = app.getAudioFrame();
            LineRenderer lineRenderer = GetComponent<LineRenderer>();

            lineRenderer.positionCount = audioFrame.samples;
            Vector3[] points = new Vector3[lineRenderer.positionCount];

            float xStretch = 8.0f;
            float yAmp = 2.0f;
            float yOffset = 4.0f;

            for(int i = 0, idx = 0; i < lineRenderer.positionCount; i++, idx+=2) {
                // AudioFrame type is PCM 16bit little endian.
                short audioLevel = (short) ( (audioFrame.buffer[idx+1] << 8) | audioFrame.buffer[idx] ); 

                points[i] = new Vector3(
                    xStretch * (2.0f * i / (lineRenderer.positionCount - 1.0f) - 1.0f), 
                    yAmp * (audioLevel / 32768f) + yOffset, 
                    0.0f
                );
            }

            lineRenderer.SetPositions(points);
        }
    }
```


#実行結果

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300377/9038bf1f-0aa1-0c7b-ac7f-a8fb0307a2b1.gif)


#最後に

[**agora.ioに関するお問い合わせはこちらから**](https://jp.vcube.com/lp/agora?utm_campaign=Agora&utm_source=Qiita&arc_id=c550154ea83427f7adde)
[<img width="912" alt="スクリーンショット 0001-08-15 13.41.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300377/5d815376-9b89-08fa-9434-6fa7e8e67bcf.png">](https://jp.vcube.com/lp/agora?utm_campaign=Agora&utm_source=Qiita&arc_id=c550154ea83427f7adde)