【Unity】マイク音量レベルメーターの実装

開発物紹介

MusiCalorieの作成にはUnityを使っています。

この中で、マイク音量を検出してレベルメーターを表示する、という事を行っていますが、Unityではあまりやられないタイプの内容で、調べてもあまり見つからなかったので、誰かの参考になればと思い実装方法を共有します。

基本設計

基本設計の概念図です。

“MicAudioSource.cs”、”LevelMeter.cs”の2つのスクリプトを用意して、それぞれで以下の内容を実装することにします。

MicAudioSource.cs

・AudioSourceにAudioClipとしてマイクデバイスのオーディオ信号を読み込ませる

・GetOutputData()でAudioSourceの信号を取得する

・取得した信号をdBに変換して、その値を保持

LevelMeter.cs

・MicAudioSourceのdB値を監視する

・dB値をuGUI Image のFillAmountの値に変換

・FillAmountの値をImageに反映し、レベルメーター画像の伸縮動作をさせる

MicAudioSource.csの実装

Unity側の準備

まずはProjectにAudioMixerを作成します。

AudioMixerを使わないと、のちのち不都合が出てしまいますので、最初に作成しておきます。

AudioMixerを開いて、”mic_input”グループを追加、ボリュームを-80dBに下げておきます。

ここが一番のポイントで、後ほどこのグループにAudioSourceを流し込むことになるのですが、

ここのボリュームを下げておかないと、マイクから入れた音声がスピーカーから出てきてしまいます。「出てきてもいいよ!」という状況であれば、そもそもAudioMixerすら使う必要もないのですが、「出てきたらマズい」という状況の場合は、これ以外の方法、例えば

・AudioSourceのVolumeを0にする

・AudioSourceをMuteにする

等で対応してしまうと、GetOutputData()に音が入ってこなくなりますので、そもそも音量が測定できなくなってしまいます。そこで行き着いたのが、一度AudioMixerまで音を流し込んでおき、AudioMixer側で音が出てこないようにする、という方法でした。

※もっと良い方法がありましたらすみません。

ボリュームを-80dBに落としておかないと、マイクから入れた音声がスピーカーから出力されてしまう

次に、HierarchyにAudioSourceを追加します。

追加したAudioSourceの設定は以下です。

Outputに先ほど作成した”mic_input”を選択しておくことが肝心です。

スクリプト

以下を先ほど作成したAudioSourceにアタッチします。

//MicAudioSource.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

class MicAudioSource : MonoBehaviour
{
    //サンプリング周波数
    static readonly int SAMPLE_RATE = 48000;

    //この秒数の幅で振幅の平均値を取ったものでdB値を更新
    static readonly float MOVING_AVE_TIME = 0.05f;

    //MOVING_AVE_TIMEに相当するサンプル数
    static readonly int MOVING_AVE_SAMPLE = (int)(SAMPLE_RATE * MOVING_AVE_TIME);

    //マイクのClipをセットする為のAudioSource
    AudioSource micAS = null;

    //現在のdB値
    private float _now_dB;
    public float now_dB { get { return _now_dB; } }

    private void Awake()
    {
        //AudioSourceコンポーネント取得
        micAS = GetComponent<AudioSource>();
    }

    void Start()
    {
        //フレーム更新開始直後にマイクデバイスをスタートする
        this.MicStart();
    }

    public void MicStart()
    {
        //AudioSourceのClipにマイクデバイスをセット
        micAS.clip = Microphone.Start(null, true, 1, SAMPLE_RATE);

        //マイクデバイスの準備ができるまで待つ
        while (!(Microphone.GetPosition("") > 0)) { }

        //AudioSouceからの出力を開始
        micAS.Play();
    }

    void Update()
    {
        if (micAS.isPlaying)
        {
            //GetOutputData用のバッファを準備
            float[] data = new float[MOVING_AVE_SAMPLE];

            //AudioSourceから出力されているサンプルを取得
            micAS.GetOutputData(data, 0);

            //バッファ内の平均振幅を取得(絶対値を平均する)
            float aveAmp = data.Average(s => Mathf.Abs(s));
            
            //振幅をdB(デシベル)に変換
            float dB = 20.0f * Mathf.Log10(aveAmp);

            //現在値(now_dB)を更新
            _now_dB = dB;

        }
    }
}

細かい説明はコード上のコメントをご確認ください。

なお、簡単のために省きましたが、AndroidやiOSだと、パーミッションの取得等を事前に行っておく必要がありますので、ご注意ください。

ポイントとしては、micAS.GetOutputData()で取得されるデータは、-1.0f~1.0fの範囲を取る、振幅値になっていますので、これをこのままレベルメーターに反映しても、よくあるdB単位のレベルメーターの動きにはなりませんので、きっちりdBに変換してあげることです。

LevelMeter.csの実装

Unity側の準備

まずはレベルメーター用のuGUI Imageを追加しましょう。

ImageのSource Imageに、Projectから画像を追加します。

画像の内容は、細長い奴でもいいですし、派手な色付きのやつ、太い奴でも、お好みで大丈夫です。

Imageの設定は以下です。Image TypeをFilledにしてください。

横長のレベルメーターを作成する場合は、「Fill Method」を「Horizontal」に、「Fill Orign」を「Left」に設定します。

スクリプト

以下のスクリプトを先ほど作成したImageにアタッチします。

//LevelMeter.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

class LevelMeter : MonoBehaviour
{
    //更新する対象のlevelMeter(uGUI Image)
    Image levelMeterImage = null;

    //このdBでlevelMeter表示の下限に到達する
    [SerializeField]
    private float dB_Min= -80.0f;

    //このdBでlevelMeter表示の上限に到達する
    [SerializeField]
    private float dB_Max = -0.0f;
    
    //dBを取得する対象のmicAudioSource
    [SerializeField]
    private MicAudioSource micAS = null;

    void Awake()
    {
        //更新する対象のImageを取得
        levelMeterImage = GetComponent<Image>();
    }

    void Update()
    {
        //dB値からlevelMeterImage用のfillAountの値に変換
        float fillAmountValue = dB_ToFillAmountValue(micAS.now_dB);

        //fillAmount値更新
        this.levelMeterImage.fillAmount = fillAmountValue;
    }

    /// <summary>
    /// dB_Minとdb_Maxに基づいてdBをfillAmount値に変換
    /// </summary>
    /// <param name="dB">dB値</param>
    /// <returns>fillAmount値</returns>
    float dB_ToFillAmountValue(float dB)
    {
        //入力されたdBをdB_MaxとdBMin値で切り捨て
        float modified_dB = dB;
        if (modified_dB > dB_Max) { modified_dB = dB_Max; }
        else if (modified_dB < dB_Min) { modified_dB = dB_Min; }

        //fillAmount値に変換(dB_Min=0.0f, dB_Max=1.0f)
        float fillAountValue = 1.0f + (modified_dB / (dB_Max - dB_Min));
        return fillAountValue;
    }

}

MicAudioSourceを監視する必要があります。インスペクタ上のLevel MeterスクリプトのMicASに、MicAudioSourceをアタッチしたAudioSourceを設定します。

動作チェック

実行してみましょう。

マイクに向かってしゃべったりしてみて、こんな感じで動けば成功と思います。

なかなかにマニアックな需要だと思いますが、参考になったらうれしいです。

コメント

タイトルとURLをコピーしました