目录:
WAV的基本知识
AudioClip概述
Microphone概述
AudioSource概述
WavUtility概述
录音保存并播放示例
参考文献
附录:WavUtility类的代码
WAV音频格式
WAV文件是在PC机平台上很常见的、最经典的多媒体音频文件,最早于1991年8月出现在Windows 3.1操作系统上,文件扩展名为WAV,是WaveFom的简写,也称为波形文件,可直接存储声音波形,还原的波形曲线十分逼真。WAV文件格式简称WAV格式是一种存储声音波形的数字音频格式,是由微软公司和IBM联合设计的,经过了多次修订,可用于Windows,Macintosh,Linix等多种操作系统
PCM编码格式
PCM编码是直接存储声波采样被量化后所产生的非压缩数据,故被视为单纯的无损耗编码格式,其优点是可获得高质量的音频信号。
基于PCM编码的WAV格式是最基本的WAV格式,被声卡直接支持,能直接存储采样的声音数据,所存储的数据能直接通过声卡播放,还原的波形曲线与原始声音波形十分接近,播放的声音质量是一流的,在Windows平台下被支持得最好,常常被用作在其它编码的文件之间转换的中间文件。PCM的缺点是文件体积过大,不适合长时间记录。正因为如此,又出现了多种在PCM编码的基础上经改进发展起来的编码格式,如:DPCM,ADPCM编码等。
与声音有关的三个参数
(1)采样频率:又称取样频率。是单位时间内的采样次数,决定了数字化音频的质量。采样频率越高,数字化音频的质量越好,还原的波形越完整,播放的声音越真实,当然所占的资源也越多。根据奎特采样定理,要从采样中完全恢复原始信号的波形,采样频率要高于声音中最高频率的两倍。人耳可听到的声音的频率范围是在16Hz-20kHz之间。因此,要将听到的原声音真实地还原出来,采样频率必须大于4 0k H z 。常用的采样频率有8 k H z 、1 1 . 02 5 k H z 、22.05kHz、44.1kHz、48kHz等几种。22.05KHz相当于普通FM广播的音质,44.1KHz理论上可达到CD的音质。对于高于48KHz的采样频率人耳很难分辨,没有实际意义。
(2)采样位数:也叫量化位数(单位:比特),是存储每个采样值所用的二进制位数。采样值反应了声音的波动状态。采样位数决定了量化精度。采样位数越长,量化的精度就越高,还原的波形曲线越真实,产生的量化噪声越小,回放的效果就越逼真。常用的量化位数有4、8、12、16、24。量化位数与声卡的位数和编码有关。如果采用PCM编码同时使用8 位声卡, 可将音频信号幅度从上限到下限化分成256个音量等级,取值范围为0-255;使用16位声卡,可将音频信号幅度划分成了64K个音量等级,取值范围为-32768至32767。
(3)声道数:是使用的声音通道的个数,也是采样时所产生的声音波形的个数。播放声音时,单声道的WAV一般使用一个喇叭发声,立体声的WAV可以使两个喇叭发声。记录声音时,单声道,每次产生一个波形的数据,双声道,每次产生两个波形的数据,所占的存储空间增加一倍。
更多WAV的知识请查看:http://www.cnblogs.com/ranson7zop/p/7657874.html.
AudioClip
顾名思义,是Unity3D中用于存放音频片段的一个类,Unity 能使用的音频格式:.aif ,.wav,.mp3,.ogg。在官方文档中(https://docs.unity3d.com/ScriptReference/AudioClip.html)中,给出了该类的属性和方法。
属性:
主要的方法:
MircoPhone
Unity中可以用microphone类进行录音,并保存到AudioClip。属性和方法如下:
AudioSource
AudioSource,是一个声音组件,用于播放音频剪辑(AudioClip)资源。以下介绍该组件的属性和方法:
AudioSource 常用属性
AudioClip(音频剪辑):指定该音频源播放哪个音频文件。
Play On Awake(在唤醒时开始播放):勾选后,在游戏运行起来以后,就会开始播放。
Loop(循环):勾选后,声音进入 “单曲循环” 状态。
Mute(静音):勾选后,静音,但音频仍处于播放状态。
Volume(音量):0:无声音;1:音量最大。
Spatial Blend(空间混合):设置声音是 2D 声音,还是 3D 声音。2D 声音没有空间的变化,3D 声音有空间的变化,离音源越近听得越明显。
Audio Source 常用函数
Play() 函数:播放音频剪辑。
Stop() 函数:停止播放。
Pause() 函数:暂停播放。
需要注意的是,m_Audio.Play()必须放在Start函数中,如果放在Update函数中也必须控制,只启发一次,不然重复处于第一音节。
Wav Utility for Unity
WavUtility是Unity中保存和读取.wav格式音频的一个类。可以通过该类的ToAudioClip函数从文件系统中加载(8,16,24和32bits).wav文件到AudiaClip对象中;通过FromAudioClip把AudioClip中的浮点数据转换成wav字节数组。
但是ToAudioClip()函数只支持用Unity Application路径去保存的数据,要想从URL中读取wav文件,可通过如下方法进行:
public void LoadAudio() { string fileName = "BXC-MC-D5-BIANXINCHEN_Photo4_9_1_audio.wav"; string path = "D://testVB//"; string url = path+fileName; Debug.Log("start load file"); StartCoroutine(LoadAudioURL(url)); } public IEnumerator LoadAudioURL(string url) { UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(url, AudioType.WAV); Yield return www.SendWebRequest(); if (www.isNetworkError) { Debug.LogWarning("Audio error:" + www.error); } else { AudioClip audioClip = ((DownloadHandlerAudioClip)把读取到的音频文件放置AudioClip里面 string filepath = ""; countIndex++; byte[] bytes = WavUtility.FromAudioClip(audioClip, out filepath, "a.wav", true);//将AudioClip保存至a.wav Debug.Log(countIndex.ToString()); }
录音示例:
private AudioClip audioClip; public int recordTime = 2; // no. of seconds can be set in Unity Editor inspector private const int sampleRate = 16000; // sample rate for recording speech public void RecordAudio () { if (Microphone.devices.Length == 0) { Debug.LogWarning ("No microphone found to record audio clip sample with."); return; } string mic = Microphone.devices [0]; audioClip = Microphone.Start (mic, false, recordTime, sampleRate); } public string SaveWavFile () { string filepath=""; byte[] bytes = WavUtility.FromAudioClip (audioClip, out filepath,"a.wav",true); return filepath; }
参考文献
[1] CSDN博客:MXShane的学习日记. Unity 3D游戏开发 - U3D进阶 | 声音组件之 AudioSource. https://blog.csdn.net/weixin_41232641/article/details/82820576.2018-09-23
[2]Unity官方文档. https://docs.unity3d.com/ScriptReference/AudioClip.html.2018-3
[3]CSDN博客:贪玩的孩纸时代.unity 录音并保存本地.
https://blog.csdn.net/yiwei151/article/details/77897742.2017-09-18
[4]博客园:greyhh. Unity中使用Microphone录音保存以及回放.
https://www.cnblogs.com/greyhh/p/6898427.html.2017-05-24
[5]博客园:nigaopeng. wav文件格式分析与详解.
http://www.cnblogs.com/ranson7zop/p/7657874.html.2017-10-12
附录:WavUtility类
using UnityEngine; using System.Text; using System.IO; using System; /// <summary> /// WAV utility for recording and audio playback functions in Unity. /// Version: 1.0 alpha 1 /// /// - Use "ToAudioClip" method for loading wav file / bytes. /// Loads .wav (PCM uncompressed) files at 8,16,24 and 32 bits and converts data to Unity's AudioClip. /// /// - Use "FromAudioClip" method for saving wav file / bytes. /// Converts an AudioClip's float data into wav byte array at 16 bit. /// </summary> /// <remarks> /// For documentation and usage examples: https://github.com/deadlyfingers/UnityWav /// </remarks> public class WavUtility { // Force save as 16-bit .wav const int BlockSize_16Bit = 2; /// <summary> /// Load PCM format *.wav audio file (using Unity's Application data path) and convert to AudioClip. /// </summary> /// <returns>The AudioClip.</returns> /// <param name="filePath">Local file path to .wav file</param> public static AudioClip ToAudioClip (string filePath) { if (!filePath.StartsWith (Application.persistentDataPath) && !filePath.StartsWith (Application.dataPath)) { Debug.LogWarning ("This only supports files that are stored using Unity's Application data path. \nTo load bundled resources use 'Resources.Load(\"filename\") typeof(AudioClip)' method. \nhttps://docs.unity3d.com/ScriptReference/Resources.Load.html"); return null; } byte[] fileBytes = File.ReadAllBytes (filePath); return ToAudioClip (fileBytes, 0); } public static AudioClip ToAudioClip (byte[] fileBytes, int offsetSamples = 0, string name = "wav") { //string riff = Encoding.ASCII.GetString (fileBytes, 0, 4); //string wave = Encoding.ASCII.GetString (fileBytes, 8, 4); int subchunk1 = BitConverter.ToInt32 (fileBytes, 16); UInt16 audioFormat = BitConverter.ToUInt16 (fileBytes, 20); // NB: Only uncompressed PCM wav files are supported. string formatCode = FormatCode (audioFormat); Debug.AssertFormat (audioFormat == 1 || audioFormat == 65534, "Detected format code '{0}' {1}, but only PCM and WaveFormatExtensable uncompressed formats are currently supported.", audioFormat, formatCode); UInt16 channels = BitConverter.ToUInt16 (fileBytes, 22); int sampleRate = BitConverter.ToInt32 (fileBytes, 24); //int byteRate = BitConverter.ToInt32 (fileBytes, 28); //UInt16 blockAlign = BitConverter.ToUInt16 (fileBytes, 32); UInt16 bitDepth = BitConverter.ToUInt16 (fileBytes, 34); int headerOffset = 16 + 4 + subchunk1 + 4; int subchunk2 = BitConverter.ToInt32 (fileBytes, headerOffset); //Debug.LogFormat ("riff={0} wave={1} subchunk1={2} format={3} channels={4} sampleRate={5} byteRate={6} blockAlign={7} bitDepth={8} headerOffset={9} subchunk2={10} filesize={11}", riff, wave, subchunk1, formatCode, channels, sampleRate, byteRate, blockAlign, bitDepth, headerOffset, subchunk2, fileBytes.Length); float[] data; switch (bitDepth) { case 8: data = Convert8BitByteArrayToAudioClipData (fileBytes, headerOffset, subchunk2); break; case 16: data = Convert16BitByteArrayToAudioClipData (fileBytes, headerOffset, subchunk2); break; case 24: data = Convert24BitByteArrayToAudioClipData (fileBytes, headerOffset, subchunk2); break; case 32: data = Convert32BitByteArrayToAudioClipData (fileBytes, headerOffset, subchunk2); break; default: throw new Exception (bitDepth + " bit depth is not supported."); } AudioClip audioClip = AudioClip.Create (name, data.Length, (int)channels, sampleRate, false); audioClip.SetData (data, 0); return audioClip; } #region wav file bytes to Unity AudioClip conversion methods private static float[] Convert8BitByteArrayToAudioClipData (byte[] source, int headerOffset, int dataSize) { int wavSize = BitConverter.ToInt32 (source, headerOffset); headerOffset += sizeof(int); Debug.AssertFormat (wavSize > 0 && wavSize == dataSize, "Failed to get valid 8-bit wav size: {0} from data bytes: {1} at offset: {2}", wavSize, dataSize, headerOffset); float[] data = new float[wavSize]; sbyte maxValue = sbyte.MaxValue; int i = 0; while (i < wavSize) { data [i] = (float)source [i] / maxValue; ++i; } return data; } private static float[] Convert16BitByteArrayToAudioClipData (byte[] source, int headerOffset, int dataSize) { int wavSize = BitConverter.ToInt32 (source, headerOffset); headerOffset += sizeof(int); Debug.AssertFormat (wavSize > 0 && wavSize == dataSize, "Failed to get valid 16-bit wav size: {0} from data bytes: {1} at offset: {2}", wavSize, dataSize, headerOffset); int x = sizeof(Int16); // block size = 2 int convertedSize = wavSize / x; float[] data = new float[convertedSize]; Int16 maxValue = Int16.MaxValue; int offset = 0; int i = 0; while (i < convertedSize) { offset = i * x + headerOffset; data [i] = (float)BitConverter.ToInt16 (source, offset) / maxValue; ++i; } Debug.AssertFormat (data.Length == convertedSize, "AudioClip .wav data is wrong size: {0} == {1}", data.Length, convertedSize); return data; } private static float[] Convert24BitByteArrayToAudioClipData (byte[] source, int headerOffset, int dataSize) { int wavSize = BitConverter.ToInt32 (source, headerOffset); headerOffset += sizeof(int); Debug.AssertFormat (wavSize > 0 && wavSize == dataSize, "Failed to get valid 24-bit wav size: {0} from data bytes: {1} at offset: {2}", wavSize, dataSize, headerOffset); int x = 3; // block size = 3 int convertedSize = wavSize / x; int maxValue = Int32.MaxValue; float[] data = new float[convertedSize]; byte[] block = new byte[sizeof(int)]; // using a 4 byte block for copying 3 bytes, then copy bytes with 1 offset int offset = 0; int i = 0; while (i < convertedSize) { offset = i * x + headerOffset; Buffer.BlockCopy (source, offset, block, 1, x); data [i] = (float)BitConverter.ToInt32 (block, 0) / maxValue; ++i; } Debug.AssertFormat (data.Length == convertedSize, "AudioClip .wav data is wrong size: {0} == {1}", data.Length, convertedSize); return data; } private static float[] Convert32BitByteArrayToAudioClipData (byte[] source, int headerOffset, int dataSize) { int wavSize = BitConverter.ToInt32 (source, headerOffset); headerOffset += sizeof(int); Debug.AssertFormat (wavSize > 0 && wavSize == dataSize, "Failed to get valid 32-bit wav size: {0} from data bytes: {1} at offset: {2}", wavSize, dataSize, headerOffset); int x = sizeof(float); // block size = 4 int convertedSize = wavSize / x; Int32 maxValue = Int32.MaxValue; float[] data = new float[convertedSize]; int offset = 0; int i = 0; while (i < convertedSize) { offset = i * x + headerOffset; data [i] = (float)BitConverter.ToInt32 (source, offset) / maxValue; ++i; } Debug.AssertFormat (data.Length == convertedSize, "AudioClip .wav data is wrong size: {0} == {1}", data.Length, convertedSize); return data; } #endregion public static byte[] FromAudioClip (AudioClip audioClip) { string file, name = ""; return FromAudioClip (audioClip, out file, name, false); } public static byte[] FromAudioClip (AudioClip audioClip, out string filepath, string filename, bool saveAsFile = true, string dirname = "records") { MemoryStream stream = new MemoryStream (); const int headerSize = 44; // get bit depth UInt16 bitDepth = 16; //BitDepth (audioClip); // NB: Only supports 16 bit //Debug.AssertFormat (bitDepth == 16, "Only converting 16 bit is currently supported. The audio clip data is {0} bit.", bitDepth); // total file size = 44 bytes for header format and audioClip.samples * factor due to float to Int16 / sbyte conversion int fileSize = audioClip.samples * BlockSize_16Bit + headerSize; // BlockSize (bitDepth) // chunk descriptor (riff) WriteFileHeader (ref stream, fileSize); // file header (fmt) WriteFileFormat (ref stream, audioClip.channels, audioClip.frequency, bitDepth); // data chunks (data) WriteFileData (ref stream, audioClip, bitDepth); byte[] bytes = stream.ToArray (); // Validate total bytes Debug.AssertFormat (bytes.Length == fileSize, "Unexpected AudioClip to wav format byte count: {0} == {1}", bytes.Length, fileSize); // Save file to persistant storage location if (saveAsFile) { filepath = string.Format ("{0}/{1}/{2}.{3}", Application.dataPath , dirname, filename + "_old", "wav"); Directory.CreateDirectory (Path.GetDirectoryName (filepath)); File.WriteAllBytes (filepath, bytes); Debug.Log ("Auto-saved .wav file: " + filepath); } else { filepath = null; } stream.Dispose (); return bytes; } #region write .wav file functions private static int WriteFileHeader (ref MemoryStream stream, int fileSize) { int count = 0; int total = 12; // riff chunk id byte[] riff = Encoding.ASCII.GetBytes ("RIFF"); count += WriteBytesToMemoryStream (ref stream, riff, "ID"); // riff chunk size int chunkSize = fileSize - 8; // total size - 8 for the other two fields in the header count += WriteBytesToMemoryStream (ref stream, BitConverter.GetBytes (chunkSize), "CHUNK_SIZE"); byte[] wave = Encoding.ASCII.GetBytes ("WAVE"); count += WriteBytesToMemoryStream (ref stream, wave, "FORMAT"); // Validate header Debug.AssertFormat (count == total, "Unexpected wav descriptor byte count: {0} == {1}", count, total); return count; } private static int WriteFileFormat (ref MemoryStream stream, int channels, int sampleRate, UInt16 bitDepth) { int count = 0; int total = 24; byte[] id = Encoding.ASCII.GetBytes ("fmt "); count += WriteBytesToMemoryStream (ref stream, id, "FMT_ID"); int subchunk1Size = 16; // 24 - 8 count += WriteBytesToMemoryStream (ref stream, BitConverter.GetBytes (subchunk1Size), "SUBCHUNK_SIZE"); UInt16 audioFormat = 1; count += WriteBytesToMemoryStream (ref stream, BitConverter.GetBytes (audioFormat), "AUDIO_FORMAT"); UInt16 numChannels = Convert.ToUInt16 (channels); count += WriteBytesToMemoryStream (ref stream, BitConverter.GetBytes (numChannels), "CHANNELS"); count += WriteBytesToMemoryStream (ref stream, BitConverter.GetBytes (sampleRate), "SAMPLE_RATE"); int byteRate = sampleRate * channels * BytesPerSample (bitDepth); count += WriteBytesToMemoryStream (ref stream, BitConverter.GetBytes (byteRate), "BYTE_RATE"); UInt16 blockAlign = Convert.ToUInt16 (channels * BytesPerSample (bitDepth)); count += WriteBytesToMemoryStream (ref stream, BitConverter.GetBytes (blockAlign), "BLOCK_ALIGN"); count += WriteBytesToMemoryStream (ref stream, BitConverter.GetBytes (bitDepth), "BITS_PER_SAMPLE"); // Validate format Debug.AssertFormat (count == total, "Unexpected wav fmt byte count: {0} == {1}", count, total); return count; } private static int WriteFileData (ref MemoryStream stream, AudioClip audioClip, UInt16 bitDepth) { int count = 0; int total = 8; // Copy float[] data from AudioClip float[] data = new float[audioClip.samples * audioClip.channels]; audioClip.GetData (data, 0); byte[] bytes = ConvertAudioClipDataToInt16ByteArray (data); byte[] id = Encoding.ASCII.GetBytes ("data"); count += WriteBytesToMemoryStream (ref stream, id, "DATA_ID"); int subchunk2Size = Convert.ToInt32 (audioClip.samples * BlockSize_16Bit); // BlockSize (bitDepth) count += WriteBytesToMemoryStream (ref stream, BitConverter.GetBytes (subchunk2Size), "SAMPLES"); // Validate header Debug.AssertFormat (count == total, "Unexpected wav data id byte count: {0} == {1}", count, total); // Write bytes to stream count += WriteBytesToMemoryStream (ref stream, bytes, "DATA"); // Validate audio data Debug.AssertFormat (bytes.Length == subchunk2Size, "Unexpected AudioClip to wav subchunk2 size: {0} == {1}", bytes.Length, subchunk2Size); return count; } private static byte[] ConvertAudioClipDataToInt16ByteArray (float[] data) { MemoryStream dataStream = new MemoryStream (); int x = sizeof(Int16); Int16 maxValue = Int16.MaxValue; int i = 0; while (i < data.Length) { dataStream.Write (BitConverter.GetBytes (Convert.ToInt16 (data [i] * maxValue)), 0, x); ++i; } byte[] bytes = dataStream.ToArray (); // Validate converted bytes Debug.AssertFormat (data.Length * x == bytes.Length, "Unexpected float[] to Int16 to byte[] size: {0} == {1}", data.Length * x, bytes.Length); dataStream.Dispose (); return bytes; } private static int WriteBytesToMemoryStream (ref MemoryStream stream, byte[] bytes, string tag = "") { int count = bytes.Length; stream.Write (bytes, 0, count); //Debug.LogFormat ("WAV:{0} wrote {1} bytes.", tag, count); return count; } #endregion /// <summary> /// Calculates the bit depth of an AudioClip /// </summary> /// <returns>The bit depth. Should be 8 or 16 or 32 bit.</returns> /// <param name="audioClip">Audio clip.</param> public static UInt16 BitDepth (AudioClip audioClip) { UInt16 bitDepth = Convert.ToUInt16 (audioClip.samples * audioClip.channels * audioClip.length / audioClip.frequency); Debug.AssertFormat (bitDepth == 8 || bitDepth == 16 || bitDepth == 32, "Unexpected AudioClip bit depth: {0}. Expected 8 or 16 or 32 bit.", bitDepth); return bitDepth; } private static int BytesPerSample (UInt16 bitDepth) { return bitDepth / 8; } private static int BlockSize (UInt16 bitDepth) { switch (bitDepth) { case 32: return sizeof(Int32); // 32-bit -> 4 bytes (Int32) case 16: return sizeof(Int16); // 16-bit -> 2 bytes (Int16) case 8: return sizeof(sbyte); // 8-bit -> 1 byte (sbyte) default: throw new Exception (bitDepth + " bit depth is not supported."); } } private static string FormatCode (UInt16 code) { switch (code) { case 1: return "PCM"; case 2: return "ADPCM"; case 3: return "IEEE"; case 7: return "μ-law"; case 65534: return "WaveFormatExtensable"; default: Debug.LogWarning ("Unknown wav code format:" + code); return ""; } } }