最近の合成音声の進歩に驚きながら、再度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としてアタッチできない…。
Comments