Unity 进阶 之 实现简单的音频可视化封装(包括音频和麦克风)

 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);
            }

        }
    }
}

Guess you like

Origin blog.csdn.net/u014361280/article/details/120618168