top of page

Unity WebGLのLipSyncに挑戦 vol.1

執筆者の写真: snackvirtualsnackvirtual

更新日:2024年12月9日

最近の合成音声の進歩に驚きながら、再度Snack virtualを構築したく思い始めた。



現在の構造は、口パクは発声データを元に音声とは別ルートで制御しているため、発声データがないと口パクしない。

発声データはCeVIO AI独特なので、合成音声はCeVIO AIから離脱できないのだ。

しかし上記のにじボイスを聞いてから、やはり発声データに依存しない口パクが必須と思い始めて、じっくりとUnityに向き合うことにした。


 

Unity上で音声再生しながら音量データを表示


まずはこれを実現した

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


public class AudioVolumeDisplay : MonoBehaviour
{
    public AudioSource audioSource;
    public AudioClip audioClip;
    public TMP_Text volumeText;
    private float[] samples = new float[48000*20];
    private int start_count = -1;
    private int samplingFrequency;
    private int sampleslength;

    void Start()
    {
        StartCoroutine(GetAudioData());
    }
    IEnumerator GetAudioData()
    {
        // 音声ファイルの読み込み
        audioSource.clip = audioClip;
        // loadされるまで待つ
        while (audioSource.clip.loadState != AudioDataLoadState.Loaded)
        {
            volumeText.text = "Wait loading";
            yield return null;
        }
        volumeText.text = "loading done";
        // 配列に取り込み
        audioClip.GetData(samples, 0);
        // サンプリング周波数取得
        samplingFrequency = audioClip.frequency;
        //sampleslength = samples.Length;
        sampleslength = 48000*20-1;
        Debug.Log("start samplingFrequency: " + samplingFrequency);
        Debug.Log("start samples.Length: " + samples.Length);

    }

    void Update()
    {
        // フレーム周波数取得
        float frameRate = 1.0f / Time.deltaTime;
        int i;
        
        if (Input.GetKeyDown(KeyCode.A)){
            audioSource.Play();
            start_count = 0;
        }
        if (start_count > -1){

            //audioSource.timeSamples = audioSource.timeSamples;

            // 音量の計算
            float sum = 0f;
            int delta = (int)(samplingFrequency / frameRate);

            for (i = start_count; i < start_count + delta; i++)
            {
                sum += Mathf.Abs(samples[i]);
            }
            float averageVolume = sum / delta;
            start_count = i;

            if (start_count + delta > sampleslength){
                start_count = -1;
            }

            // 音量の表示
//            volumeText.text = "Volume: " + averageVolume.ToString("F4");
            volumeText.text = "Volume: " + averageVolume.ToString("F4") + "\nPosition: " + start_count.ToString() + "\nsamples[20000]= " + samples[20000];
            Debug.Log("Position: " + start_count.ToString() + "\nsamples[20000]= " + samples[20000]);
        }


    }
}

ちなみにこのコードは

        // loadされるまで待つ
        while (audioSource.clip.loadState != AudioDataLoadState.Loaded)
        {
            volumeText.text = "Wait loading";
            yield return null;
        }

がすでにWebGL対応になっていることに注意。


実際の結果(音量が小さいので注意)



PC上で実行した場合、音声と同期してVolume値が変化していることがわかるだろう。

これをWebGLで実行すると、このようになる。



これを見るとわかる通り、音量データは音声から多少遅れている。

またAudioClipのデータ配列もローカルで実行したときと違う値を示している。


これはブラウザをリロードしても、ブラウザをEdgeからChromeに変えても同じだった。



 

AudioClipのデータ配列のずれの原因を探る


このようなコードに書き換えて、AudioClip.GetData(samples,0)で得られた音声データ配列を分析した。

また解析用の音声ファイルは、44100Hzサンプリングの400Hz10秒+600Hz10秒+無音10秒の30秒ファイルとした。

    void Start()
    {
        StartCoroutine(GetAudioData());
    }
    IEnumerator GetAudioData()
    {
        // 音声ファイルの読み込み
        audioSource.clip = audioClip;
        // loadされるまで待つ
        while (audioSource.clip.loadState != AudioDataLoadState.Loaded)
        {
            volumeText.text = "Wait loading";
            yield return null;
        }
        volumeText.text = "loading done";

        // AudioClipの長さに基づいてsamples配列を初期化
        sampleslength = audioClip.samples * audioClip.channels;
        samples = new float[sampleslength];

        // 配列に取り込み
        audioClip.GetData(samples, 0);

        // サンプリング周波数取得
        samplingFrequency = audioClip.frequency;

        // wavファイルは44.1KHz sampling、1秒が400Hz正弦波、次の1秒が600Hz正弦波、最後の1秒が無音
        // 負から正への0点交差位置を検出し、それの距離と回数を表示し、ファイル上の周波数を確認する

        int prev_zero_position = 0, current_zero_position = 0;
        int i,j;
        int[] count = new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
        int[] period = new int[]{ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };

        for (i = 5; i < sampleslength-5; i++){

            bool skip_flag = false;

            // 立ち上がりのゼロ交点を探索
            if ((samples[i-1] < 0)&&(samples[i+1] > 0)){
                prev_zero_position = current_zero_position;
                current_zero_position = i;
                int distance = current_zero_position - prev_zero_position;
                // 周期とその回数をカウント
                for (j = 0; j < 15; j++){
                    if (period[j] == distance){
                        count[j] ++;
                        skip_flag = true;
                        break;
                    }
                }
                if (!skip_flag){
                    for (j = 0; j < 15; j++){
                        if (period[j] == 0){
                            period[j] = distance;
                            count[j] ++;
                            break;
                        }
                    }
                }

            }

        }

        volumeText.text = "sampleslength = " + sampleslength 
                        + "\nsample freq = " + samplingFrequency;
        
        for (j = 0; j < 15; j++){
            volumeText.text += "\nperiod"+ j.ToString() + " = " + period[j] + "   count"+ j.ToString() + " = " + count[j];
        };

    }
}

PC上で実行した場合:



これらの値から計算すると

・音はファイルのとおり頭から出ている

・最初の音の周波数は44.1KHz/109=404Hz と想定通り

・次の音の周波数は44.1Kz/73=604Hz と想定通り

・ふたつの音の時間は882004/44.1KHz=20.00Sec と想定どおり



WebGLの場合:



これらの値から計算すると

・音はファイルの頭から10020サンプル遅れて始まっている

・最初の音の周波数は44.1KHz/119=370Hz と想定より7.6%低い

・次の音の周波数は44.1Kz/79=558Hz と想定より7.6%低い

・ふたつの音の時間は949484/44.1KHz=21.53Sec と想定より7.6%長い


WebGLの場合、すべての音声はいったんAACに圧縮され、これが解凍されて再生されているような記述があった。

とすれば、最初の頭の無音はAACを解凍している時間なのかもしれない。

またサンプリング周波数が異なっている

audioClip.frequencyでは「44.1KHz」と得られるが、

実際には44.1Kz*1.076=47.45KHzというとても中途半端な数値となっている。


これはWebGLでwavファイルをリサンプリングしている可能性があるとしか思えない

それにしてもこのサンプリング周波数は謎である。

またサンプリング周波数とサンプル数はアタッチしたwavファイルの情報と思われる

だから音の開始と周波数がずれるのだろう。


では、音声ファイルをAACにしてみたらどうなるのか?…と思ったが、UnityでaacファイルをAudioClipとしてアタッチできない…。




閲覧数:8回0件のコメント

最新記事

すべて表示

xAIML SUNABA

Comments


bottom of page