?Unity 进阶 之 实现简单的音频可视化封装(包括音频和麦克
目录
?Unity 进阶 之 实现简单的音频可视化封装(包括音频和麦克
一、简单介绍
二、实现原理
三、注意事项
四、效果预览
五、实现步骤
六、关键脚本
七、附加 获取每帧中的最大值,可视化到GameObject上
一、简单介绍
Unity 工具类,自己整理的一些游戏开发可能用到的模块,单独独立使用,方便游戏开发。
本节介绍,获取音频或者麦克风的数据,进行简单的可视化显示封装,如果你有更好的方法,欢迎留言交流。
二、实现原理
1、关键是获取 音频数据的 float[] 数据;
2、可以使用 AudioSource.GetSpectrumData() 获取;
3、也可以使用 AudioSource.clip.GetData() 获取(例如 Microphone 麦克风的 float[] 数据)
4、通过获取的 float[] 数据,动态赋值到物体上,进行可视化显示(这里是动态改变 Image 比例和颜色来动态演示)
三、注意事项
1、麦克风使用不同的接口获取数据 float[] 可能呈现的效果不同,需要根据实际情况选择调整
2、AudioSource.GetSpectrumData() 的 float[] 数据有数组长度要求(至少64)
3、麦克风 Microphone 可能需要权限获取
四、效果预览
五、实现步骤
1、打开 Unity,新建一个空工程
2、简单步骤一下场景,这里使用UI Image 来进行可视化展示(32个 Image 条)
?
3、在工程中新建脚本,包括音频采样数据类,数据可视化类,麦克风工具管理类,以及一个音频数据叠加到数据可视化管理类,外加一个测试音频
4、把 TestAudioVisualizerManager 挂载到场景中,并对应赋值数据
5、运行场景,效果如上
?
六、关键脚本
1、TestAudioVisualizerManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace AudioVisualizer.Test {
public class TestAudioVisualizerManager : MonoBehaviour
{
public AudioClip MusicAudioClip;
public Button MusicPPButton;
public Button MicrophonePPButton;
/// <summary>
/// UIList
/// </summary>
public List<Image> MusicUIList = new List<Image>();
public List<Image> MicrophoneUIList = new List<Image>();
IAudioSample mMusicAudioSampler;
IUIImageAudioSampleVisualizer mMusicUIImageAudioSampleVisualizer;
IAudioSample mMicrophoneAudioSampler;
IUIImageAudioSampleVisualizer mMicrophoneUIImageAudioSampleVisualizer;
MicrophoneAudioManager mMicrophoneAudioManager;
private void Start()
{
SetMusicAudioClip();
SetMicrophoneAudioClip();
}
private void Update()
{
mMusicUIImageAudioSampleVisualizer?.UpdateVisualzation();
mMicrophoneUIImageAudioSampleVisualizer?.UpdateVisualzation();
}
#region Music
void SetMusicAudioClip()
{
int samplesLength = MusicUIList.Count * 2;
mMusicAudioSampler = new AudioSampler(this.gameObject, MusicAudioClip, samplesLength);
mMusicUIImageAudioSampleVisualizer = new MusicUIImageAudioSampleVisualizer(mMusicAudioSampler, MusicUIList);
MusicPPButton.GetComponentInChildren<Text>().text = "Pause";
MusicPPButton.onClick.AddListener(() => {
if (mMusicAudioSampler.IsPlaying())
{
mMusicAudioSampler.Pause();
MusicPPButton.GetComponentInChildren<Text>().text = "Play";
}
else
{
mMusicAudioSampler.Play();
MusicPPButton.GetComponentInChildren<Text>().text = "Pause";
}
});
}
#endregion
#region Microphone
void SetMicrophoneAudioClip()
{
MicrophoneInit();
MicrophonePPButton.GetComponentInChildren<Text>().text = "Pause";
MicrophonePPButton.onClick.AddListener(() => {
if (mMicrophoneAudioSampler.IsPlaying())
{
mMicrophoneAudioSampler.Pause();
MicrophonePPButton.GetComponentInChildren<Text>().text = "Play";
mMicrophoneAudioManager?.StopMicorphone();
}
else
{
mMicrophoneAudioSampler.Play();
MicrophonePPButton.GetComponentInChildren<Text>().text = "Pause";
MicrophoneInit();
}
});
}
void MicrophoneInit() {
int samplesLength = MusicUIList.Count * 2;
mMicrophoneAudioManager = new MicrophoneAudioManager();
mMicrophoneAudioSampler = new AudioSampler(this.gameObject, mMicrophoneAudioManager.StartMicorphone(0), samplesLength);
mMicrophoneUIImageAudioSampleVisualizer = new MicrophoneUIImageAudioSampleVisualizer(mMicrophoneAudioSampler, MicrophoneUIList, mMicrophoneAudioManager.GetCurMicrophoneDevice());
}
#endregion
}
}
2、IAudioSample
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AudioVisualizer {
/// <summary>
/// 这个是简单的音频采样操作相关
/// </summary>
public interface IAudioSample
{
/// <summary>
/// 音频播放
/// </summary>
void Play();
/// <summary>
/// 播放指定音频
/// </summary>
/// <param name="audioClip"></param>
void Play(AudioClip audioClip);
/// <summary>
/// 音频暂停
/// </summary>
void Pause();
/// <summary>
/// 音频停止
/// </summary>
void Stop();
/// <summary>
/// 音频是否正在播放
/// </summary>
/// <returns></returns>
bool IsPlaying();
/// <summary>
/// 获取音频数据数组
/// </summary>
/// <param name="channel"></param>
/// <param name="window"></param>
/// <returns></returns>
float[] GetAudioSamples(int channel, FFTWindow window);
/// <summary>
/// 麦克风的区别于一般音频的数据数组(数据采样方法稍微不一样而已)
/// </summary>
/// <param name="microphoneDevice"></param>
/// <param name="sampleCount"></param>
/// <param name="maxVolume"></param>
/// <returns></returns>
float[] GetMicrophoneAudioSamples(string microphoneDevice, int sampleCount,out float maxVolume);
}
}
3、AudioSampler
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AudioVisualizer {
public class AudioSampler : IAudioSample
{
AudioSource mMusicAudioSource;
private float[] mSamples; // 大于64
public AudioSampler(GameObject attachGO,AudioClip musicAudioClip, int samplesLength)
{
mMusicAudioSource = attachGO.AddComponent<AudioSource>();
mSamples = new float[samplesLength];
Play(musicAudioClip);
}
public void Play()
{
if (mMusicAudioSource?.clip == null)
{
Debug.LogError(GetType() + "/Play()/ AudioSource.clip can not be null");
}
mMusicAudioSource?.Play();
}
public void Play(AudioClip audioClip)
{
mMusicAudioSource.clip = audioClip;
if (mMusicAudioSource?.isPlaying==true)
{
Stop();
}
Play();
}
public void Pause()
{
mMusicAudioSource?.Pause();
}
public void Stop()
{
mMusicAudioSource?.Stop();
}
public bool IsPlaying()
{
return (bool)mMusicAudioSource?.isPlaying;
}
public float[] GetAudioSamples(int channel, FFTWindow window)
{
mMusicAudioSource?.GetSpectrumData(mSamples, 0, FFTWindow.BlackmanHarris);
if (mSamples.Length <= 0 || mSamples == null)
{
Debug.LogError(GetType() + "/GetAudioSamples()/ AudioSource‘ s Samples Data can not be null");
return null;
}
return mSamples;
}
/// <summary>
/// 每一振处理那一帧接收的音频文件
/// </summary>
/// <param name="microphoneDevice"></param>
/// <param name="sampleCount"></param>
/// <param name="maxVolume"></param>
/// <returns></returns>
public float[] GetMicrophoneAudioSamples(string microphoneDevice,int sampleCount, out float maxVolume)
{
//剪切音频
maxVolume = 0;
float[] volumeData = new float[sampleCount];
int offset = Microphone.GetPosition(microphoneDevice) - sampleCount;
if (offset < 0)
{
return null;
}
mMusicAudioSource.clip.GetData(volumeData, offset);
for (int i = 0; i < sampleCount; ++i)
{
float wavePeak = volumeData[i];
if (maxVolume < wavePeak)
maxVolume = wavePeak;
}
return volumeData;
}
}
}
4、IUIImageAudioSampleVisualizer
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace AudioVisualizer {
/// <summary>
/// 这个是在 UI Image 上的可视化
/// </summary>
public class IUIImageAudioSampleVisualizer
{
/// <summary>
/// 音频频率数组
/// </summary>
protected float[] mSamples;
/// <summary>
/// UIList
/// </summary>
protected List<Image> mUiList = new List<Image>();
/// <summary>
/// 下降的幅度比值
/// (变化反应灵敏度?)
/// </summary>
[Range(1, 30)]
protected float UpLerp = 25; // 根据实际情况调整
/// <summary>
/// 音频采样接口
/// </summary>
protected IAudioSample mAudioSample;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="audioSample"></param>
/// <param name="uiList"></param>
public IUIImageAudioSampleVisualizer(IAudioSample audioSample, List<Image> uiList)
{
mAudioSample = audioSample;
mUiList = uiList;
Init();
}
/// <summary>
/// 初始化
/// </summary>
public virtual void Init() { }
/// <summary>
/// 获取采样数据
/// (默认方法,子类可以继承修改)
/// </summary>
public virtual void UpdateVisualzation()
{
//获取频谱
mSamples = mAudioSample.GetAudioSamples(0, FFTWindow.BlackmanHarris);
//循环
for (int i = 0; i < mUiList.Count; i++)
{
//使用Mathf.Clamp将中间位置的的y限制在一定范围,避免过大
//频谱时越向后越小的,为避免后面的数据变化不明显,故在扩大samples[i]时,乘以50+i * i*0.5f
Vector3 _v3 = mUiList[i].transform.localScale;
float clmpValue = Mathf.Clamp(mSamples[i] * (50 + i * i * 0.5f), 0, 50);
_v3 = new Vector3(1, clmpValue, 1);
// 简单的比例调整
mUiList[i].transform.localScale = Vector3.Lerp(mUiList[i].transform.localScale, _v3, Time.deltaTime * UpLerp);
// 简单的颜色变化
mUiList[i].GetComponent<Image>().color = new Color(0.01f * i, clmpValue / 8, clmpValue / 4, 1);
}
}
}
}
5、MicrophoneUIImageAudioSampleVisualizer
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace AudioVisualizer {
/// <summary>
/// 麦克风
/// </summary>
public class MicrophoneUIImageAudioSampleVisualizer : IUIImageAudioSampleVisualizer
{
int mSampleCount;
string mMicrophoneDevice;
float mMaxVolumePerFrame; // 获取的的每帧音频数据中的最大值
public MicrophoneUIImageAudioSampleVisualizer(IAudioSample audioSample, List<Image> uiList, string microphoneDevice) :base(audioSample, uiList) {
mSampleCount = mUiList.Count;
mMicrophoneDevice = microphoneDevice;
}
public override void UpdateVisualzation()
{
mSamples = mAudioSample.GetMicrophoneAudioSamples(mMicrophoneDevice, mSampleCount, out mMaxVolumePerFrame);
if (mSamples != null) // 正常显示
{
for (int i = 0; i < mSampleCount; i++)
{
float tmp = mSamples[i];
Vector3 v3 = new Vector3(1, (tmp * 10 + 0.2f) * 2, 1);
mUiList[i].gameObject.transform.localScale = Vector3.Lerp(mUiList[i].gameObject.transform.localScale, v3, Time.deltaTime * UpLerp);//将可视化的物体和音波相关联
mUiList[i].gameObject.transform.GetComponent<Image>().color = new Color(mSamples[i] * 10 + 0.2f, 1 - (i) / (float)mSampleCount, (i) / (float)mSampleCount, 1);//将可视化的物体和音波相关联
}
}
else { //恢复看不见
for (int i = 0; i < mSampleCount; i++)
{
Vector3 _v3 = new Vector3(1, 0, 1);
mUiList[i].gameObject.transform.localScale = Vector3.Lerp(mUiList[i].gameObject.transform.localScale, _v3, Time.deltaTime * UpLerp);//将可视化的物体和音波相关联
mUiList[i].gameObject.transform.GetComponent<Image>().color = new Color(0, 1 - (i) / (float)mSampleCount, (i) / (float)mSampleCount, 1);//将可视化的物体和音波相关联
}
}
}
}
}
6、MusicUIImageAudioSampleVisualizer?????
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace AudioVisualizer {
/// <summary>
/// 音频音乐
/// </summary>
public class MusicUIImageAudioSampleVisualizer : IUIImageAudioSampleVisualizer
{
public MusicUIImageAudioSampleVisualizer(IAudioSample audioSample, List<Image> uiList) : base(audioSample, uiList) {
}
public override void UpdateVisualzation()
{
//获取频谱
mSamples = mAudioSample.GetAudioSamples(0, FFTWindow.BlackmanHarris);
//循环
for (int i = 0; i < mUiList.Count; i++)
{
//使用Mathf.Clamp将中间位置的的y限制在一定范围,避免过大
//频谱时越向后越小的,为避免后面的数据变化不明显,故在扩大samples[i]时,乘以0+i * i*0.5f
Vector3 _v3 = mUiList[i].transform.localScale;
float clmpValue = Mathf.Clamp(mSamples[i] * (5 + i * i * 0.5f), 0, 5);
_v3 = new Vector3(1, clmpValue, 1);
// 简单的比例调整
mUiList[i].transform.localScale = Vector3.Lerp(mUiList[i].transform.localScale, _v3, Time.deltaTime * UpLerp);
// 简单的颜色变化
mUiList[i].GetComponent<Image>().color = new Color(0.01f * i, clmpValue / 8, clmpValue / 4, 1);
}
}
}
}
???
七、附加 获取每帧中的最大值,可视化到GameObject上
1、效果如下
2、添加修改脚本
1)MusicUIImageAudioSampleVisualizer
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace AudioVisualizer {
/// <summary>
/// 音频音乐
/// </summary>
public class MusicUIImageAudioSampleVisualizer : IUIImageAudioSampleVisualizer
{
public MusicUIImageAudioSampleVisualizer(IAudioSample audioSample, List<Image> uiList) : base(audioSample, uiList) {
}
public override void UpdateVisualzation()
{
//获取频谱
mSamples = mAudioSample.GetAudioSamples(0, FFTWindow.BlackmanHarris);
UIImageVisualization();
SphereVisualization();
}
void UIImageVisualization()
{
//循环
for (int i = 0; i < mUiList.Count; i++)
{
//使用Mathf.Clamp将中间位置的的y限制在一定范围,避免过大
//频谱时越向后越小的,为避免后面的数据变化不明显,故在扩大samples[i]时,乘以0+i * i*0.5f
Vector3 _v3 = mUiList[i].transform.localScale;
float clmpValue = Mathf.Clamp(mSamples[i] * (5 + i * i * 0.5f), 0, 5);
_v3 = new Vector3(1, clmpValue, 1);
// 简单的比例调整
mUiList[i].transform.localScale = Vector3.Lerp(mUiList[i].transform.localScale, _v3, Time.deltaTime * UpLerp);
// 简单的颜色变化
mUiList[i].GetComponent<Image>().color = new Color(0.01f * i, clmpValue / 8, clmpValue / 4, 1);
}
}
GameObject mSphere;
void SphereVisualization()
{
float maxVolumePerFrame = 0;
for (int i = 0; i < mSamples.Length; i++)
{
if (maxVolumePerFrame< mSamples[i])
{
maxVolumePerFrame = mSamples[i];
}
}
if (mSphere == null)
{
mSphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
mSphere.transform.position = Vector3.up*4.5f;
}
else
{
Vector3 tmp = Vector3.one + Vector3.one * maxVolumePerFrame * 10;
mSphere.transform.localScale = Vector3.Lerp(mSphere.gameObject.transform.localScale, tmp, Time.deltaTime * UpLerp);
mSphere.transform.GetComponent<Renderer>().material.color = new Color(maxVolumePerFrame*10, 0, 1 - maxVolumePerFrame, 1);
}
}
}
}
2)MicrophoneUIImageAudioSampleVisualizer
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace AudioVisualizer {
/// <summary>
/// 麦克风
/// </summary>
public class MicrophoneUIImageAudioSampleVisualizer : IUIImageAudioSampleVisualizer
{
int mSampleCount;
string mMicrophoneDevice;
float mMaxVolumePerFrame; // 获取的的每帧音频数据中的最大值
public MicrophoneUIImageAudioSampleVisualizer(IAudioSample audioSample, List<Image> uiList, string microphoneDevice) :base(audioSample, uiList) {
mSampleCount = mUiList.Count;
mMicrophoneDevice = microphoneDevice;
}
public override void UpdateVisualzation()
{
mSamples = mAudioSample.GetMicrophoneAudioSamples(mMicrophoneDevice, mSampleCount, out mMaxVolumePerFrame);
UIImageVisualization();
SphereVisualization();
}
void UIImageVisualization() {
if (mSamples != null) // 正常显示
{
for (int i = 0; i < mSampleCount; i++)
{
float tmp = mSamples[i];
Vector3 v3 = new Vector3(1, (tmp * 10 + 0.2f) * 2, 1);
mUiList[i].gameObject.transform.localScale = Vector3.Lerp(mUiList[i].gameObject.transform.localScale, v3, Time.deltaTime * UpLerp);//将可视化的物体和音波相关联
mUiList[i].gameObject.transform.GetComponent<Image>().color = new Color(mSamples[i] * 10 + 0.2f, 1 - (i) / (float)mSampleCount, (i) / (float)mSampleCount, 1);//将可视化的物体和音波相关联
}
}
else
{ //恢复看不见
for (int i = 0; i < mSampleCount; i++)
{
Vector3 _v3 = new Vector3(1, 0, 1);
mUiList[i].gameObject.transform.localScale = Vector3.Lerp(mUiList[i].gameObject.transform.localScale, _v3, Time.deltaTime * UpLerp);//将可视化的物体和音波相关联
mUiList[i].gameObject.transform.GetComponent<Image>().color = new Color(0, 1 - (i) / (float)mSampleCount, (i) / (float)mSampleCount, 1);//将可视化的物体和音波相关联
}
}
}
GameObject mSphere;
void SphereVisualization() {
if (mSphere == null)
{
mSphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
}
else {
Vector3 tmp = Vector3.one +Vector3.one * mMaxVolumePerFrame * 10;
mSphere.transform.localScale = Vector3.Lerp(mSphere.gameObject.transform.localScale, tmp, Time.deltaTime * UpLerp);
mSphere.transform.GetComponent<Renderer>().material.color = new Color(mMaxVolumePerFrame, 0.5f, 0, 1);
}
}
}
}
|