Unity3D-声音处理

目录:

WAV的基本知识

AudioClip概述

Microphone概述

AudioSource概述

WavUtility概述

录音保存并播放示例

参考文献

附录:WavUtility类的代码

 

WAV音频格式

        WAV文件是在PC机平台上很常见的、最经典的多媒体音频文件,最早于19918月出现在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.05kHz44.1kHz48kHz等几种。22.05KHz相当于普通FM广播的音质,44.1KHz理论上可达到CD的音质。对于高于48KHz的采样频率人耳很难分辨,没有实际意义。

(2)采样位数:也叫量化位数(单位:比特),是存储每个采样值所用的二进制位数。采样值反应了声音的波动状态。采样位数决定了量化精度。采样位数越长,量化的精度就越高,还原的波形曲线越真实,产生的量化噪声越小,回放的效果就越逼真。常用的量化位数有48121624。量化位数与声卡的位数和编码有关。如果采用PCM编码同时使用位声卡可将音频信号幅度从上限到下限化分成256个音量等级,取值范围为0-255;使用16位声卡,可将音频信号幅度划分成了64K个音量等级,取值范围为-3276832767

(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)中,给出了该类的属性和方法。

属性:

2_20190113012039_891.png

主要的方法:

3_20190113012105_694.png

 

MircoPhone

Unity中可以用microphone类进行录音,并保存到AudioClip。属性和方法如下:

4_20190113012144_150.png

 

 

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

WavUtilityUnity中保存和读取.wav格式音频的一个类。可以通过该类的ToAudioClip函数从文件系统中加载(8,16,2432bits.wav文件到AudiaClip对象中;通过FromAudioClipAudioClip中的浮点数据转换成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 "";
        }
    }
 
}

 

 


上一篇:
下一篇:

首页 所有文章 机器人 计算机视觉 自然语言处理 机器学习 编程随笔 关于