【问题标题】:Unity 2019 - How can I save mixer audio output to an AudioClip or load a newly saved .wav to an audioclip at runtime?Unity 2019 - 如何在运行时将混音器音频输出保存到音频剪辑或将新保存的 .wav 加载到音频剪辑?
【发布时间】:2019-08-23 20:33:07
【问题描述】:

我正在使用游戏中的乐器和录音设备制作游戏。我希望用户能够演奏乐器并将它们录制到音频剪辑和 wav 文件中,然后他们可以将其作为自己演奏另一种乐器的伴奏进行播放,再次记录输出,允许他们制作音轨组合仪器。到目前为止,我一切正常,除了我无法弄清楚如何获取音频剪辑 - 我将 .wav 文件保存到资产文件夹中,但我对代码的理解不足以找出将音频剪辑的数据设置到的位置正在写入的数据。

我在这里使用 Olkor 的代码将录音保存为 wav,效果很好:https://unitylist.com/p/za/Output-Audio-Recorder

我从中保存音频剪辑的两个选项似乎是a)在保存到磁盘的同时保存到音频剪辑(我无法弄清楚)或将其保存到磁盘然后将其加载到游戏中作为音频剪辑 - 使用我尝试执行的 Unity Web 请求,但我得到一个错误,要么无法访问已中止的 DownloadHandlerAudioClip 的 .audioclip 属性,要么如果我延迟调用该函数来加载文件,解码时出错声音的。无论哪种方式,我的资产文件夹中都有一个保存的 .wav 音频文件,但没有音频剪辑。

InvalidOperationException: Cannot access the .audioClip property of an aborted DownloadHandlerAudioClip
UnityEngine.Networking.DownloadHandlerAudioClip.GetContent 
(UnityEngine.Networking.UnityWebRequest www) (at 






C:/buildslave/unity/build/Modules/UnityWebRequestAudio/Public/DownloadHandler 
   Audio.bindings.cs:49)
   OutputAudioRecorder+<GetAudioClip>d__27.MoveNext () (at 
   Assets/Scripts/OutputAudioRecorder.cs:202)
   UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator 
   enumerator, System.IntPtr returnValueAddress) (at C:/buildslave/unity/build/Runtime/Export/Scripting/Coroutines.cs:17)

我正在使用的下面的脚本附加到音频侦听器,因此它会从游戏中获取所有音频输出。我已经研究过使用 audioListener GetOutputData 做一些事情并将其传递给 audioClip SetData 但我无法弄清楚,我承认这超出了我目前的能力 - 我真的很感激对如何解决这个问题的一些见解以任何可能的方式。

using System;
        using System.IO;
        using UnityEngine;
        using UnityEngine.Networking;
        using System.Collections;


            public class OutputAudioRecorder : MonoBehaviour
            {
                public const string DEFAULT_FILENAME = "record";
                public const string FILE_EXTENSION = ".wav";

                public bool IsRecording { get { return recOutput; } }

                private int bufferSize;
                private int numBuffers;
                private int outputRate;
                private int headerSize = 44; //default for uncompressed wav
                private String fileName;
                private bool recOutput = false;
                private AudioClip newClip;
                private FileStream fileStream;
                private AudioClip[] audioClips;
                private AudioSource[] audioSources;
                public int currentSlot;
                float[] tempDataSource;

                void Awake()
                {
                    outputRate = AudioSettings.outputSampleRate;

                }

                void Start()
                {
                    AudioSettings.GetDSPBufferSize(out bufferSize, out numBuffers);
                    audioSources = new AudioSource[3]; 
                    audioSources[0] = GameObject.FindWithTag("RecSlot1").GetComponent<AudioSource>();
                    audioSources[1] = GameObject.FindWithTag("RecSlot2").GetComponent<AudioSource>();
                    audioSources[2] = GameObject.FindWithTag("RecSlot3").GetComponent<AudioSource>();
                }

                public void StartRecording(string recordFileName)
                {
                    fileName = Path.GetFileNameWithoutExtension(recordFileName) + FILE_EXTENSION;


                    if (!recOutput)
                    {
                        StartWriting(fileName);
                        recOutput = true;
                    }
                    else
                    {
                        Debug.LogError("Recording is in progress already");
                    }
                }

                public void StopRecording()
                {
                    recOutput = false;
                    WriteHeader();
                    UpdateClip();

                }


                private void StartWriting(String name)
                {
                    fileStream = new FileStream(Application.dataPath + "/" + name, FileMode.Create);

                    var emptyByte = new byte();
                    for (int i = 0; i < headerSize; i++) //preparing the header
                    {
                        fileStream.WriteByte(emptyByte);
                    }
                }

                private void OnAudioFilterRead(float[] data, int channels)
                {
                    if (recOutput)
                    {
                        ConvertAndWrite(data); //audio data is interlaced
                    }
                }

                private void ConvertAndWrite(float[] dataSource)
                {

                    var intData = new Int16[dataSource.Length];
                    //converting in 2 steps : float[] to Int16[], //then Int16[] to Byte[]

                    var bytesData = new Byte[dataSource.Length * 2];
                    //bytesData array is twice the size of
                    //dataSource array because a float converted in Int16 is 2 bytes.

                    var rescaleFactor = 32767; //to convert float to Int16

                    for (var i = 0; i < dataSource.Length; i++)
                    {
                        intData[i] = (Int16)(dataSource[i] * rescaleFactor);
                        var byteArr = new Byte[2];
                        byteArr = BitConverter.GetBytes(intData[i]);
                        byteArr.CopyTo(bytesData, i * 2);
                    }

                    fileStream.Write(bytesData, 0, bytesData.Length);

                    tempDataSource = new float[dataSource.Length];
                    tempDataSource = dataSource;


                }

                private void WriteHeader()
                {

                    fileStream.Seek(0, SeekOrigin.Begin);

                    var riff = System.Text.Encoding.UTF8.GetBytes("RIFF");
                    fileStream.Write(riff, 0, 4);

                    var chunkSize = BitConverter.GetBytes(fileStream.Length - 8);
                    fileStream.Write(chunkSize, 0, 4);

                    var wave = System.Text.Encoding.UTF8.GetBytes("WAVE");
                    fileStream.Write(wave, 0, 4);

                    var fmt = System.Text.Encoding.UTF8.GetBytes("fmt ");
                    fileStream.Write(fmt, 0, 4);

                    var subChunk1 = BitConverter.GetBytes(16);
                    fileStream.Write(subChunk1, 0, 4);

                    UInt16 two = 2;
                    UInt16 one = 1;

                    var audioFormat = BitConverter.GetBytes(one);
                    fileStream.Write(audioFormat, 0, 2);

                    var numChannels = BitConverter.GetBytes(two);
                    fileStream.Write(numChannels, 0, 2);

                    var sampleRate = BitConverter.GetBytes(outputRate);
                    fileStream.Write(sampleRate, 0, 4);

                    var byteRate = BitConverter.GetBytes(outputRate * 4);

                    fileStream.Write(byteRate, 0, 4);

                    UInt16 four = 4;
                    var blockAlign = BitConverter.GetBytes(four);
                    fileStream.Write(blockAlign, 0, 2);

                    UInt16 sixteen = 16;
                    var bitsPerSample = BitConverter.GetBytes(sixteen);
                    fileStream.Write(bitsPerSample, 0, 2);

                    var dataString = System.Text.Encoding.UTF8.GetBytes("data");
                    fileStream.Write(dataString, 0, 4);

                    var subChunk2 = BitConverter.GetBytes(fileStream.Length - headerSize);
                    fileStream.Write(subChunk2, 0, 4);

                    fileStream.Close();

                }

                void UpdateClip()
                {
                    StartCoroutine(GetAudioClip());

                }

                IEnumerator GetAudioClip()
                {
                    using (UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip("file://" + Application.dataPath + "myRecord1.wav", AudioType.WAV))
                    {
                        yield return www.Send();

                        if (www.isNetworkError)
                        {
                            Debug.Log(www.error);
                        }
                        else
                        {
                            AudioClip newClip = DownloadHandlerAudioClip.GetContent(www);

                            Debug.Log(newClip.name + "name    " + newClip.length);
                            keyboardScript.audioClip = newClip;
                        }
                    }
                }

【问题讨论】:

    标签: c# unity3d audio filestream audioclip


    【解决方案1】:

    尝试使用以下方法加载为音频剪辑:

            private IENumerator LoadAudio(string url) {
    
            WWW www;           
            www = new WWW("file:///" + url);  
    
            yield return www;
    
            if (www != null && www.isDone)
            {
                AudioClip audioClip;
                audioClip = www.GetAudioClip(true, false, AudioType.WAV);
            }
            }
    

    这至少对我有用。

    【讨论】:

    • 感谢您的回答!我刚刚尝试过,Unity 说它已经过时了 - 使用 WWW 而是建议我使用我正在使用的对我不起作用的东西 - (UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip("file://" + Application.dataPath + "myRecord1 .wav", AudioType.WAV)) 我正在运行您的示例,但它什么也没做。没有错误,但也没有创建音频剪辑。
    • 没问题!您是否检查过该网址是否有效?你什么时候运行脚本?在wav文件写完之前你不能使用它。
    • 我已经尝试了三种方式的 URL:使用 file://、file:/// 并且只是引用它保存到的路径加上文件名。我在记录 wav 的脚本中的停止录制函数之后调用了 LoadAudio 函数 - 我尝试以很长的延迟调用该函数以确保它已经被写入,但我可以看到文件夹中的文件早在调用 LoadAudio 的函数运行 - 但它仍然没有输出音频剪辑。
    【解决方案2】:

    认为这是解决方案,但在运行时仍无法加载。 (它只是指剪辑的旧副本,直到我重新加载游戏。一开始没有注意到这一点。)

    虽然我无法让网络请求为我工作或让 WWW 工作,但我确实得到了我想要的效果 通过在保存 .wav 的 assets 文件夹中创建一个空白剪辑,然后将其拖动到属性窗口中作为参考,将音频剪辑设置为流式传输,然后使用上面的代码编写 wav,它会自动用我记录的数据。 音频有一点失真(噼啪声),我接下来会研究一下。

    我知道这个解决方案可能非常明显(我想我必须给统一一些东西来参考但没有意识到)并且如果它们没有预定义,则不能解决在运行时加载文件的问题像我一样,但它至少解决了我的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多