最近一直找游戏设计灵感。

找着找着,突然想起,以前玩过的一款打飞机游戏,端游,他可以根据你播放的音乐文件来自动发射子弹,玩家不是能自主发射子弹的,当时我就一直拿那个:《劲乐团》里的一首曲子:《V3》- 贝多芬交响曲。

这首曲子,全程高能,整个屏幕都是子弹,爽啊,哈哈,而且这首曲子也很嗨。

当然与音乐相关的游戏还不止这款。。。

相关游戏:

  • 劲舞团(韩国T3 娱乐公司开发,游戏开发技术没有与音乐有关,这游戏只是编辑定时弹出什么QTE的按键序列而已)
  • 劲乐团(韩国O2Media游戏公司开发,游戏开发技术应该是用到了声乐文件的音频spectrum图谱的分析,先自动生成一个节奏点相关的QTE数据,然后再人工审核一下生产的内容,可减少开发成本,因为如果全程人工编辑,那个编辑人力成本太高了)
  • 节奏大师(同上,腾讯光速工作室开发)
  • QQ音速(同上,腾讯代理,韩国seed9开发)
  • 某些游戏也会根据我们的音波来加速移动,发射子弹等。这些就不一一介绍了,玩过想过的太多了。

等等,还有现在新出的各式各样的游戏。基本都会使用到先音频分析自动生成数据,再人工调整。(当然,我只是猜测,肯定有团队是全人工编辑的)

为了找灵感,再尝试试API来制作一些功能,再未来开发,也许有帮助。

OK,扯题的话就不说了。

运行总体效果如下图:(没有声音)

录制

Microphone类使用到的几个API

  • public static string[] devices { get; }
    获取可用的麦克风的设备名称数组
  • public static AudioClip Start(string deviceName, bool loop, int lengthSec, int frequency);
    使用指定deviceName设备开始录制
  • public static void End(string deviceName);
    指定deviceName设备结束录制
  • public static int GetPosition(string deviceName);
    获取麦克风当前对应设备写入录制缓存的索引位置
  • public static bool IsRecording(string deviceName);
    获取指定deviceName设备名称是否当前录制中

OK,就这么几个是主要的。

录制步骤

  • 先检测有没设备Microphone.devices.Length > 0
  • 开始录制调用Microphone.Start,要传入使用的设备名,是否环形循环录制缓存,录制缓存支持的时长,录制的采样率
    • 设备名就Microphone.devices里头其一
    • 是否环形循环录制缓存意思就是是否录制数据写到尾部是,是从将写入指标重置会开头(0索引)
    • 录制缓存支持的时长,就是如果不是环形循环录制的话,那么录制时间到了,就不会再录制
    • 录制采样率,这个决定了录制出来声音的质量,采样率越高当然就越接近原声
      • 但采样率不能乱设置,下面会相关的知识点讲到采样率是什么东东
  • 等待录制时间到达,就自动取消录制(或时手动的在时间到达之前结束录制)
  • 播放刚刚录制的AudioClip,已测试是否有效。
    • 如果听不到声音,请检查:

      • 播放该AudioClip的AudioSource的Volume(音量);
      • 如果AudioSource的Spatial Blend是2D的,那就确定场景中至少有一个AudioListener组件时启用的。
      • 如果AudioSource的Spatial Blend是3D的,那就确定启用的AudioListener与你播放的AudioSource的距离保持在AudioSource的MinDistance与MaxDistance之前。
      • 在调用Microphone.Start中的frequency确定是在能听到的范围,一般设置在:1000~44100之间就可以了(小于100的几乎都听不到,之前不小心设置错了Slider.Value的值为1,传了个1过去,导致录制出来的AudioClip没有任何声音)
      • 还是听不到,那就确保你的音响设备的音量,与设备是否正常。

相关的知识点

  • 声频:指的是声波介质,每秒震动的次数,单位交:赫兹,英文:Hz,人类能听到的声频大概是:20Hz~20000Hz。低于20Hz我们叫超低音声波,高于20000Hz我们叫超高音波(超音波)。而不同物种,他们声频生物传感器是不一样的,如:蝙蝠,相对人类来说的部分超低高音都可以听到,但它能听到的范围也是有限的。
  • 声波:而声波是介质震动(一般指的是空气)时的振动波,现实生活中我们听到的声音都是一些连续不间断的声波,声波进入了我们的耳朵(接受声波处理的生物传感器),就听到声音了
  • 采样率:因为声波是连续不间断的,但我们记录的是离散的数字信号,采样率就是每秒记录的次数。如:44100次/每秒,就是我们常见的44KHz。但是采样率越高记录所需要的数据量就越大。相反,越低的采样率,数据量就很少了,但是音质会很差,甚至低到一定程度,我们都几乎听不到。我们平时看到保安人员的对讲机,大概就是1KHz,我们一些高音质的MP3等音频文件有16KHz,32KHz,44KHz,都有,有些电影院的那些可能上MHz都是有的。
  • 比特率:和采样率差不多的意思,只是他主要表达的是每秒需要记录用到的bit位有多少,考量维度不一样,前者是记录次数,后者是使用bit的量,就可以很准确的确定数据占用量。

音波图(不是声谱Spectrum)

至于声谱图,我没去研究过,这里不涉及。

AudioClip类API

  • public bool GetData(float[] data, int offsetSamples);
    从AudioClip中,指定offsetSamples的位置开始读取音波数据存入我们预先创建的float[] data缓存中。

AudioClip是我们的音频对象。

  • Microphone.Start返回的是AudioClip。
  • Project视图管理资源中,音频文件也是AudioClip。

而我们就是通过这个AudioClip.GetData来取对应缓存位置记录的音波数据的,然后根据这些数据我们将它用于控制一些粒子特效,或是线条,几何体,颜色变化,等来表现我们的音波图的。

下图就是根据此API制作的简单的音波图。(我放了很多个:RawImage,然后用AudioClip.GetData取出来的数据分别控制他们的宽度,及颜色即可,这首史诗级曲子:EI Dorado也是我非常喜欢的,有兴趣可以听听)

扩展

  • 播放器,KTV,KTV评分系统:有些前面哪些功能,起始如果上对各音频文件的编解码处理,你甚至可以制作一些播放器了(在搞一些文件读写就好了),录制的话,你要可以搞个人KTV录制。还可以分析音波与原唱的音波图相似度来评KTV录制结果分数。

  • 无时间限制录制文件功能:还有一个,起始还可以写一个无时间长度限制的录音软件。
    但是还需要对代码调整(就是判断Microphone.GetPosition差不多到缓存尾部的时候,马上再调用Microphone.Start记录重新录制就好了,再将之前录制的那个AudioClip的数据写入文件流啊,就可以了)

  • 简单语音录制的聊天系统:游戏,或是应用中的简单语音录制的聊天系统,前面讲了怎么怎么录制,怎么怎么从AudioClip拿数据,还有,下面的Code中,也有显示AudioClip.SetData怎么怎么使用,那么就可以将GetData的float[]转到byte[],在通过Socket的网络传输到服务器,服务器再广播,其他接受的客户端先将byte[]转float[],再就AudioClip.SetData,再AudioSource.Clip = 你的Clip,再AudioSource.Play,完事。

Code

using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// author      :   jave.lin
/// date        :   2020.02.18
/// 测试录制麦克风的各种功能
/// </summary>
public class RecordMicrophoneSoundScript : MonoBehaviour
{public enum RecordStatusType{Unstart,Recording,End,}public enum RecordErrorType{None,NotFoundDevice,RecordingError,}[Space(10)]public Text playSoundWaveText;public AudioClip music;[Space(10)]public Text startOrEndRecordBtnText;public Text statusText;public Text detailStatusText;public Text deviceCaptureFreqText;[Space(10)]public Dropdown deviceDropdownList;public Dropdown frequencyDropdownList;public Slider recordTimeSlider;public Text recordTimeText;public Text playProgressText;public Text recordTimeProgressText;public Slider playProgressSlider;public Slider recordTimeProgressSlider;public Text detailRecordClipText;[Space(10)]public Color32 lineLowCol = new Color32(1, 1, 0, 1);public Color32 lineHightCol = new Color32(1, 0, 0, 1);public int lineW;public RawImage[] lines;[Space(10)][SerializeField] [ReadOnly] private RecordStatusType recordStatus;[SerializeField] [ReadOnly] private RecordErrorType errorStatus;[SerializeField] [ReadOnly] private AudioClip recordedClip;private AudioSource audioSource;            // 播放的sourceprivate bool startRecord;private int posWhenEnd;private float[] bufferHelper;[SerializeField] private float[] soundWaveBuffer;[SerializeField] private float[] soundWaveBuffer2;private void Awake(){audioSource = GetComponent<AudioSource>();soundWaveBuffer = new float[lines.Length];soundWaveBuffer2 = new float[lines.Length];}private void Start(){deviceDropdownList.ClearOptions();deviceDropdownList.AddOptions(new List<string>(Microphone.devices));deviceDropdownList.value = 0;deviceDropdownList.RefreshShownValue();for (int i = 0; i < lines.Length; i++) SetLine(i, 2, 1);}private void Update(){UpdateAllStatus();}private void SetLine(int idx, float multiplier = 2.0f, float t = 0.1f){var line = lines[idx];var v = line.rectTransform.sizeDelta;var data = soundWaveBuffer[idx];v.x = Mathf.Lerp(v.x, lineW * data * multiplier, t);line.rectTransform.sizeDelta = v;line.color = Color.Lerp(lineLowCol, lineHightCol, Mathf.Abs(data));}// 更新音波数据,这里我就不叫spectrumprivate void UpdateSoundWaveData(AudioClip clip, int pos){if (clip != null){var offsetPos = pos - lines.Length;if (offsetPos > 0) clip.GetData(soundWaveBuffer, offsetPos);    // 如果clip当前录制位置,截取够lines.length的数量else{// 如果不够// 先截取尾部的作为起始数据clip.GetData(soundWaveBuffer, clip.samples + offsetPos);var delta = lines.Length + offsetPos;if (delta > 0){// 再截取补长的数据,再clip前面部分// 因为录制时,我们开启了环形缓存// public static AudioClip Start(string deviceName, bool loop, int lengthSec, int frequency)中的第二个参数决定,我们写死trueclip.GetData(soundWaveBuffer2, 0);Array.Copy(soundWaveBuffer2, 0, soundWaveBuffer, -offsetPos, delta);}}playSoundWaveText.text = $"PlaySoundWave : {clip.name}";}}// 更新音波的可视线private void UpdateSoundLines(){for (int i = 0; i < lines.Length; i++) SetLine(i);}// 更新一些状态private void UpdateAllStatus(){if (Microphone.devices.Length == 0){errorStatus = RecordErrorType.NotFoundDevice;return;}else{var deviceName = deviceDropdownList.captionText.text;Microphone.GetDeviceCaps(deviceName, out int minFreq, out int maxFreq);deviceCaptureFreqText.text = $"Device Capture MinFreq:{minFreq} MaxFreq:{maxFreq}";var isRecording = Microphone.IsRecording(deviceName);if (isRecording){recordStatus = RecordStatusType.Recording;// 使用GetPosition获取写入缓存的位置与缓存大小的比例值,再乘以录制秒数,就可以得到准确的录制时长var pos = Microphone.GetPosition(deviceName);var recordProgress = recordedClip.length * ((float)pos / recordedClip.samples);recordTimeProgressText.text = $"{recordProgress.ToString("00.0")}";recordTimeProgressSlider.value = recordProgress;// update sound wave ( not a spectrum )UpdateSoundWaveData(recordedClip, pos); // 更新录制的音波数据UpdateSoundLines();                     // 更新音波的可视线}else{if (recordedClip == null){recordStatus = RecordStatusType.Unstart;}else{recordStatus = RecordStatusType.End;}}startOrEndRecordBtnText.text = startRecord ? "EndRecord" : "StartRecord";var recordPostion = Microphone.GetPosition(deviceName);// Debug.Log($"deviceName:{deviceName} isRecording:{isRecording} recordPosition:{recordPostion}");detailStatusText.text = $"deviceName:{deviceName} isRecording:{isRecording} recordPosition:{recordPostion}";statusText.text = $"Status : {recordStatus}, Error:{errorStatus}";if (recordedClip != null){detailRecordClipText.text = $"RecordClipInfo:len:{recordedClip.length},freq:{recordedClip.frequency},channels:{recordedClip.channels},samples:{recordedClip.samples},ambisonic:{recordedClip.ambisonic},loadType:{recordedClip.loadType},preloadAudioData:{recordedClip.preloadAudioData},loadInBg:{recordedClip.loadInBackground},loadState:{recordedClip.loadState}";}if (audioSource.clip != null){//Debug.Log($"audioSource.timeSamples:{audioSource.timeSamples}");playProgressSlider.minValue = 0;playProgressSlider.maxValue = audioSource.clip.length;playProgressSlider.value = audioSource.time;playProgressText.text = $"{audioSource.time.ToString("00.0")}";var percent = audioSource.time / audioSource.clip.length;UpdateSoundWaveData(audioSource.clip, (int)(percent * audioSource.clip.samples));   // 更新播放的音波数据UpdateSoundLines();             // 更新音波的可视线}else{playProgressSlider.value = playProgressSlider.minValue;}}}// 给DropDown控制绑定的On Value Change 的事件方法public void OnDeviceItemSelectionChanged(int idx){var selection = deviceDropdownList.options.Count > 0 ? deviceDropdownList.options[idx].text : "EMPTY";Debug.Log($"Devices DropDownList Item Selection Changed, idx : {idx}, selection : {selection}");}public void OnStartOrEndRecordBtnClick(){if (Microphone.devices.Length == 0){errorStatus = RecordErrorType.NotFoundDevice;return;}var deviceName = deviceDropdownList.captionText.text;var isRecording = Microphone.IsRecording(deviceName);if (isRecording)    // 录制中{posWhenEnd = Microphone.GetPosition(deviceName);// 那么就取消录制 - cancel recordingMicrophone.End(deviceName);recordStatus = RecordStatusType.End;startRecord = false;}else                // 没在录制{if (recordedClip != null) Destroy(recordedClip);// 人耳可以听到的声频为:20Hz~20000Hz左右// 音频采样频率(即:离散的采样点组合的模拟声波震动频率(每秒多少次,单位名:赫兹:Hz))// 经自己的测试发现:// 8820‬ = 44100 / 5:和微信的录制频率差不多(甚至微信的更低,数据小啊)// 11025 = 44100 / 4 ~ 44100:都差不多,不认真听都差不多// 22050 = 44100 / 2// 44100// 88200 = 44100 * 2// 频率越高,音质越高,但占用数据越大var frequency = Convert.ToInt32(frequencyDropdownList.captionText.text);recordedClip = Microphone.Start(deviceName, true, (int)recordTimeSlider.value, frequency);startRecord = true;recordTimeProgressSlider.minValue = 0;recordTimeProgressSlider.maxValue = (int)recordTimeSlider.value;recordTimeProgressSlider.value = 0;if (recordedClip == null) errorStatus = RecordErrorType.RecordingError;}}public void OnPlayClipBtnClick(){if (recordedClip != null){var playingClip = AudioClip.Create("MicrophoneRecord", posWhenEnd, recordedClip.channels, recordedClip.frequency, recordedClip.loadType == AudioClipLoadType.Streaming);if (bufferHelper == null || bufferHelper.Length < posWhenEnd) bufferHelper = new float[posWhenEnd];recordedClip.GetData(bufferHelper, 0);playingClip.SetData(bufferHelper, 0);// 通过上面对playingClip的重新构建,playingClip.length是最精准的了就不是我们固定的长度了audioSource.clip = playingClip;audioSource.Play();}}public void OnPauseBtnClick(){audioSource.Pause();}public void OnStopPlayClipBtnClick(){audioSource.Stop();}public void OnRecorddTimeSliderValueChanged(float value){// recordTimeText.text = $"{value}s"; // 这里value一直未0有BUGrecordTimeText.text = $"{recordTimeSlider.value}s";}public void OnPlayMusicBtnClick(){audioSource.clip = music;audioSource.Play();}
}

Project

RecordingMicrophoneSoundTesting_2019_3_1f1

Unity使用:2019.3.1f1,为何要讲这个,因为我讲2019.3.1f1降版本到2018.3.0f2的unity后,Scene里记录的Hierachy的对象绑定的各种Component的数据都丢失了。

本想用2019来制作测试,拿回2018发布到手机测试的,结果。。。。。。咳咳。。。(因为2019发布版本目前会卡Building Gradle Project的问题,官方网络限制中国区域的问题,需要翻墙,梯子,你才能正常发布,而现在VPN限制这么严,所以就没去用2019发布,暂时没那这个录制功能到手机端上测试)

Unity - Microphone 测试录音,播放录音,显示音波图(不是Spectrum图)相关推荐

  1. Android--实现自制录音/播放录音程序

    首先,让我们先看下实现的截图: 当有录音文件存在时,会显示在下面的ListView当中. 下面给出实现的完整代码: 1.主程序代码 package irdc.ex07_11;import java.i ...

  2. Android实现可录音/暂停录音/播放录音的录音软件

    一.功能简介与操作视频 该APP功能完成音频的录制并命名保存与播放功能 1.录制 在音频录制界面点击开始按钮即可进行录制,录制过程中可以点击暂停按钮暂时停止录制,暂停可以继续录制,点击停止按钮结束录制 ...

  3. iOS语音消息功能实现,录音/播放录音

    // // ViewController.m // VoiceMessage // // Created by 李伟宾 on 2017/11/28. // Copyright © 2017年 liwe ...

  4. android 录音与播放录音 带根据音频大小动态效果

    先看看效果图:         首先是布局文件: <?xml version="1.0" encoding="utf-8"?> <Linear ...

  5. uni —app 录音_uni-app 录音(十六)

    uni.getRecorderManager() 获取全局唯一的录音管理器 recorderManager. 平台支持 小程序 5+APP recorderManager 对象的方法列表 方法 参数 ...

  6. unity3d录音播放

    直接挂载到组件上面,打包apk测试即可 using UnityEngine;/// <summary>/// 录音播放/// </summary> public class M ...

  7. android录音播放并上传

    最近研究了下录音上传,各位有需要可参考下,如有不妥欢迎指出 <pre name="code" class="html">package com.ki ...

  8. 微信小程序 用wx.getRecorderManager()和wx.createInnerAudioContext()制作一个简单的录音播放器(录制以及播放)

    在开发的时候公司提出了一个如题的要求,发现微信官方文档里好像没有相关的组件,就自己做了一个简单的,可以实现用户录制音频,播放和暂停的需求.放上来供大家参考 预览 wxml <view>&l ...

  9. 利用Python实现录音播放并翻译,真正的实时进行翻译

    文章目录 有了它,实现实时翻译还远吗? 一.还有3秒到达战场 二.效果展示 四.调用API接口的准备工作 五.开发过程详细介绍 (一)准备工作(二)开发1.界面部分2.音频录制部分的开发(2)reco ...

最新文章

  1. 图灵奖得主LeCun和7位华人博士当选美国科学院2021院士!!
  2. LA3266田忌赛马
  3. Java类加载文章1(z)
  4. iview的走马灯嵌套在模态框中,宽度为0的解决方案
  5. 阿里一面 —— 什么是多线程?
  6. 点击链接如何直接跳转到相对应的聊天窗口
  7. bat 存储过程返回值_MySQL-存储过程和函数详述
  8. 2020-python小工能
  9. C Primer Plus第三章总结
  10. C语言之perror()与sterror()用法(十九)
  11. 【优化求解】基于NSGA2算法求解多目标优化问题matlab源码
  12. Dell笔记本周期性闪屏故障
  13. C语言开发FlyBird小游戏,飞翔小鸟小游戏,可以直接运行!
  14. DWZ 富文本编辑器 IE下失去焦点
  15. 数据模型的作用和数据模型的三个要素:
  16. 计算机软件高级职称有啥用,计算机软考高级职称有什么用
  17. VLAN的概念与配置
  18. Collecting Bugs (DP期望)
  19. 大专生程序员找工作的一点小建议 知识分享 经验分享
  20. 如何正确使用物业安全巡检系统

热门文章

  1. java之spring中时间戳转换时间方式
  2. 2016/10/1 国庆新更
  3. R语言使用epiDisplay包的shapiro.qqnorm函数执行Shapiro-Wilk检验并可视化QQ图、整合假设检验和可视化结果判断数据是否符合正态分布(图中包含假设检验的p值)
  4. RTKLIB Manual:Options
  5. 灾难性故障 (异常来自 HRESULT:0x8000FFFF (E_UNEXPECTED))
  6. CTSAPIO2019爆零记
  7. Vue 获取应用的版本号
  8. Windows环境下怎么在文件夹下打开cmd命令行
  9. recycleView瀑布流
  10. js 实现倒计时(短信验证码倒计时)