最近一直在看一个开源项目,UnityChan,主要是Unity娘在华丽的舞台上跳舞,效果非常酷炫,就去看了一下里面一些技术的实现原理,感觉受益匪浅,所以在此记录一下。
Unity娘跳舞的舞台灯光效果非常华丽,而且会随着音乐的节奏闪烁,这里面用到了K神(Keijiro Takahashi,Unity社群的知名人物,在Github上有很多开源项目)的音频插件,其作用是解析音频输出的数据,然后再转换成舞台上的灯光表现,用个比较时髦的词即:音乐可视化。
关于此君更多的介绍可以浏览这个网址:http://home.51.com/1979572713/diary/wxitem/455643.html
本文主要将探究音频解析插件Reaktor的实现原理,以及延伸出的一些想法。
音乐可视化
一种以视觉为核心,以音乐为载体,借助多种新媒体技术等传播媒介,通过画面、影像来诠释音乐内容的技术。其本质是将音频数据,通过一定的规则,转换成图像或其它表现形式。在UnityChan中,舞台灯光的变化会跟随音乐的节奏,就是通过这种方式实现的。
获取音频数据
读了K神的代码后才知道,原来在Unity的MonoBehavior中,有这样一个生命周期,它会在每一帧去解析音频源文件,读取到这一帧应该播放的音频数据,然后以浮点数的形式,放到一个数组里,然后播放器会解析数组里面的数据进行播放。我们通过OnAudioFilterRead这个函数,去获取到每一帧的数据,然后对其进行解析,生成波形。
1
2
3
4
5
6
7
8
9
10
11
12
void OnAudioFilterRead(float[] data, int channels)
{
for (var i = 0; i < data.Length; i += 1)
{
var level = data[i];
squareSum += (level * level); //记录声音数据
}
sampleCount += data.Length / channels;//记录码率
if (mute)
for (var i = 0; i < data.Length; i++)
data[i] = 0; //静音
}
就这么简单?获取音频数据的任务就完成了,接下来需要对获取到的这些数据进行一些处理。
解析音频数据
虽然我们每一帧都能拿得到庞大的音频数据,但是如果不知道这些数据是干什么用的就很难对其进行解析,好在有K神的代码在,读就完事了。
首先,我找到一条公式:
dbLevel = 20.0f * Mathf.Log10(rms / refLevel + zeroOffset);
这里得到的应该是分贝等级(变量命名),至于为什么要这么算,不清楚,这里面的计算太复杂了,可能我需要去看一些音频解码的知识,才能对其进行解读。
其中rms变量是由我们前面获取到的数据计算而来:
var rms = Mathf.Min(1.0f, Mathf.Sqrt(squareSum / sampleCount));
refLevel和zeroOffset是定义好的常量:
1
2
const float zeroOffset = 1.5849e-13f;
const float refLevel = 0.70710678118f; // 1/sqrt(2)
到此,K神所需要的主要数据已经解析完了,实际上他只要每一帧的分贝数据就够了,因为他后面是通过分贝强度来控制灯光闪烁的。
表现形式转换
到了这一步,其实大致已经猜得到K神接下来要做什么了。首先,每个灯的物体上有一个Animator组件,这是用来控制灯闪烁的动画的。
通过这个状态机,我们可以控制动画播放的速度,以及强度。然后在Update函数里,通过读取Reaktor输出的解析出来的声音信息,对动画进行动态调整。
1
2
3
4
5
6
7
void Update()
{
if (speed.enabled)
animator.speed = speed.Evaluate(reaktor.Output);
if (trigger.Update(reaktor.Output))
animator.SetTrigger(triggerName);
}
到此,通过音频控制灯光闪烁的任务就完成了。
是不是So Easy?其实看了K神的代码之后,发现确实挺简单的,但是里面的一些公式,比如计算分贝强度的公式,还是挺难搞懂的。不过应用层面已经没有任何问题了,而且读了他的代码之后发现,其实还可以有很多“骚”操作。
一些想法
其实这个插件的应用远不止于此,接下来我会介绍一些联想到的骚操作和一些探索。
声音变速、变调
在获取音频数据那里,我们可以拿到每一帧的音频数据,然后对其进行解析,但是我还发现,K神的代码里有一个静音的功能,而他所做的仅仅是把数组里的所有数据置为0,这让我产生了浓厚的兴趣,假如我所做的不是将其置为零而是对它进行其他的修改,是不是就可以改变声音本来的样子,实现变速甚至变调?
然后我进行了一系列的实验。
把每个数据扩大十倍。声音变大了,但是音调并没有变化,而且有的地方听起来特别燥。
每个数据增加或减小一个值。和扩大十倍的效果差不多。
好了,由于对这些数据的认知基本为0,所以我也想不出什么好法子去处理它们,于是我去GitHub上找插件。
很快我找到一个名为SoundTouch的插件,它的作用是根据你输入的音频数据,和设定好的参数,对数据进行处理,实时返回你一个处理完的数据。真是完美符合我的要求。
我们需要在初始化时设置一些静态的参数:
1
2
3
4
5
6
7
soundTouch.SetChannels(1);
soundTouch.SetSampleRate(44100);
soundTouch.SetSetting(SettingId.UseQuickseek, 0);
soundTouch.SetSetting(SettingId.UseAntiAliasFilter, 0);
soundTouch.SetSetting(SettingId.SequenceDurationMs, 40);
soundTouch.SetSetting(SettingId.SeekwindowDurationMs, 15);
soundTouch.SetSetting(SettingId.OverlapDurationMs, 8);
大致就是通道数,码率之类的。
然后需要实时调整的参数:
1
2
soundTouch.SetPitch(pitch); //音调
soundTouch.SetRate(rate); //速度
最后在MonoBehaviour的生命周期函数OnAudioFilterRead里完成音频数据的处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void OnAudioFilterRead(float[] data, int channels)
{
float[] tempsample_after = new float[data.Length];
soundTouch.PutSamples(data, data.Length);//输入数据
soundTouch.ReceiveSamples(tempsample_after, data.Length);//得到处理后的数据
for (var i = 0; i < data.Length; i += 1)
{
var level = data[i];
if (enableSampleMode)
{
data[i] = tempData[i];
}
else
{
data[i] = tempsample_after[i];//修改原始数据
}
}
if (mute)
for (var i = 0; i < data.Length; i++)
data[i] = 0;
}
到此,音频的变调与变速功能已经实现了,其实这个SoundTouch插件还有很多其他的功能,这个就有待以后研究啦。
不过有一个问题,变调之后的声音会出现一些噪点,不知道是插件本身算法有问题,还是我用法有问题,总之,如何降噪是下一步急需解决的问题。
直接读取音频文件
之前提到的,在unity生命周期里获取音频数据的方法,这个数据是unity内部封装好的数据,而非原始数据,所以就缺乏一些自由度,想要随心所欲的修改和播放,我们就需要自己获取音频文件的数据,写一个音乐播放器?不存在的,太麻烦了,unity为我们提供了一函数,可以直接获取AudioClip的数据:
1
2
var result = m_audioClip.GetData(tempData, m_curFrame * m_readSample);
m_curFrame++;
其中,tempData为获取到的数据,它是一个float数组,后面的参数是读取数据时的偏移,我在这做了一个处理,即每一帧偏移2048个单位。
将这里读取到的数据,直接放到OnAudioFilterRead的输出Data中,就可以实现播放音频的效果。
这里需要手动调整每一帧偏移的单位数量,这取决于原音频文件,因为不同的音频文件的比特率是不一样的。调到合适的大小,就可以听到还算是不错的效果。但是依然会有一些白噪声,猜测是在数据中存在一些标记位,被我直接当成音频来读了。这一点还有待研究。
音频控制粒子发射器
在UnityChan中,K神插件的应用仅仅是用来控制播放动画的速度和强度,但其实我们解析出来的音频数据,完全可以用来控制粒子系统,因为粒子系统的粒度足够大,可以更细致的表现音乐的效果,从而实现更华丽的表现。
关于这一点,其实K神自己也有很多研究,在他的网站上有很多Demo了,非常酷炫。
音频控制动画系统
这是还在YY中的操作,假如我们把unity娘跳舞的动画裁成很多片段,然后通过解析音频数据来控制跳舞动画的播放,是不是就可以实现通过音乐控制unity娘跳舞的功能了?不过这个还只是一个YY,完全没有实践,如果后面我成功了,会在这里更新的。
总结
K神对音频解析的方法以及通过音乐控制灯光的思路,为我打开了新世界的大门,通过这样的方式,可以用音乐做很多事,使音乐与游戏的结合更加紧密,甚至合为一体。而对音频数据的处理,又使得音频变调或者变速变得更加容易。
后面会继续读UnityChan这个项目得源码,相信会有更多收获。