• Unity运行时加载外部mp3/wav音频


    本文介绍Unity开发中,在运行时加载外部音频(mp3/wav)的方法,非 WWW 或 UnityWebRequest,需要www方式的同学请自行baidu

    参考库:

    1. NAudio:功能全,但仅限windows平台
    2. NLayer:读取mp3音频文件并解析,正好满足需求;

    github地址(都是NAudio名下的)

    1. NAudio: GitHub - naudio/NAudio: Audio and MIDI library for .NET
    2. NLayer: GitHub - naudio/NLayer: MPEG 1 & 2 Decoder for Layers 1, 2, & 3

    NAudio

            输入:mp3文件

            输出:AudioClip

    1. // Use NAudio
    2. // file => mp3 byte[] => {NAudio} => wav byte[] => AudioClip
    3. using System;
    4. using System.IO;
    5. using NAudio.Wave;
    6. // parse mp3
    7. AudioClip LoadMp3Audio(byte[] audioBytes, string fileName)
    8. {
    9. MemoryStream mp3stream = new MemoryStream(audioBytes);
    10. Mp3FileReader mp3audio = new Mp3FileReader(mp3stream);
    11. WaveStream waveStream = WaveFormatConversionStream.CreatePcmStream(mp3audio);
    12. MemoryStream outputStream = new MemoryStream();
    13. using (WaveFileWriter waveFileWriter = new WaveFileWriter(outputStream, waveStream.WaveFormat))
    14. {
    15. byte[] bytes = new byte[waveStream.Length];
    16. waveStream.Position = 0;
    17. waveStream.Read(bytes, 0, Convert.ToInt32(waveStream.Length));
    18. waveFileWriter.Write(bytes, 0, bytes.Length);
    19. waveFileWriter.Flush();
    20. }
    21. byte[] wavBytes = outputStream.ToArray();
    22. return LoadWavAudio(wavBytes, fileName);
    23. }
    24. // parse wav
    25. AudioClip LoadWavAudio(byte[] audioBytes, string fileName)
    26. {
    27. // WAV 自定义wav解析类
    28. WAV wav = new WAV(audioBytes);
    29. AudioClip audioClip;
    30. if (wav.ChannelCount == 2)
    31. {
    32. audioClip = AudioClip.Create(fileName, wav.SampleCount, 2, wav.Frequency, false);
    33. audioClip.SetData(wav.StereoChannel, 0);
    34. }
    35. else
    36. {
    37. audioClip = AudioClip.Create(fileName, wav.SampleCount, 1, wav.Frequency, false);
    38. audioClip.SetData(wav.LeftChannel, 0);
    39. }
    40. return audioClip;
    41. }
    42. // 逻辑不完整,但是能用,所以只要你和代码一个能跑就可以了
    43. public class WAV
    44. {
    45. // convert two bytes to one float in the range -1 to 1
    46. static float bytesToFloat(byte firstByte, byte secondByte)
    47. {
    48. // convert two bytes to one short (little endian)
    49. short s = (short)((secondByte << 8) | firstByte);
    50. // convert to range from -1 to (just below) 1
    51. return s / 32768.0F;
    52. }
    53. static int bytesToInt(byte[] bytes, int offset = 0)
    54. {
    55. int value = 0;
    56. for (int i = 0; i < 4; i++)
    57. {
    58. value |= ((int)bytes[offset + i]) << (i * 8);
    59. }
    60. return value;
    61. }
    62. // properties
    63. public float[] LeftChannel { get; internal set; }
    64. public float[] RightChannel { get; internal set; }
    65. public float[] StereoChannel { get; internal set; }
    66. public int ChannelCount { get; internal set; }
    67. public int SampleCount { get; internal set; }
    68. public int Frequency { get; internal set; }
    69. public WAV(byte[] wav)
    70. {
    71. // Determine if mono or stereo
    72. ChannelCount = wav[22]; // Forget byte 23 as 99.999% of WAVs are 1 or 2 channels
    73. // Get the frequency
    74. Frequency = bytesToInt(wav, 24);
    75. // Get past all the other sub chunks to get to the data subchunk:
    76. int pos = 12; // First Subchunk ID from 12 to 16
    77. // Keep iterating until we find the data chunk (i.e. 64 61 74 61 ...... (i.e. 100 97 116 97 in decimal))
    78. while (!(wav[pos] == 100 && wav[pos + 1] == 97 && wav[pos + 2] == 116 && wav[pos + 3] == 97))
    79. {
    80. pos += 4;
    81. int chunkSize = wav[pos] + wav[pos + 1] * 256 + wav[pos + 2] * 65536 + wav[pos + 3] * 16777216;
    82. pos += 4 + chunkSize;
    83. }
    84. pos += 8;
    85. // Pos is now positioned to start of actual sound data.
    86. SampleCount = (wav.Length - pos) / 2; // 2 bytes per sample (16 bit sound mono)
    87. if (ChannelCount == 2) SampleCount /= 2; // 4 bytes per sample (16 bit stereo)
    88. // Allocate memory (right will be null if only mono sound)
    89. LeftChannel = new float[SampleCount];
    90. if (ChannelCount == 2) RightChannel = new float[SampleCount];
    91. else RightChannel = null;
    92. // Write to double array/s:
    93. int i = 0;
    94. while (pos < wav.Length)
    95. {
    96. LeftChannel[i] = bytesToFloat(wav[pos], wav[pos + 1]);
    97. pos += 2;
    98. if (ChannelCount == 2)
    99. {
    100. RightChannel[i] = bytesToFloat(wav[pos], wav[pos + 1]);
    101. pos += 2;
    102. }
    103. i++;
    104. }
    105. //Merge left and right channels for stereo sound
    106. if (ChannelCount == 2)
    107. {
    108. StereoChannel = new float[SampleCount * 2];
    109. //Current position in our left and right channels
    110. int channelPos = 0;
    111. //After we've changed two values for our Stereochannel, we want to increase our channelPos
    112. short posChange = 0;
    113. for (int index = 0; index < (SampleCount * 2); index++)
    114. {
    115. if (index % 2 == 0)
    116. {
    117. StereoChannel[index] = LeftChannel[channelPos];
    118. posChange++;
    119. }
    120. else
    121. {
    122. StereoChannel[index] = RightChannel[channelPos];
    123. posChange++;
    124. }
    125. //Two values have been changed, so update our channelPos
    126. if (posChange % 2 == 0)
    127. {
    128. if (channelPos < SampleCount)
    129. {
    130. channelPos++;
    131. //Reset the counter for next iterations
    132. posChange = 0;
    133. }
    134. }
    135. }
    136. }
    137. else
    138. {
    139. StereoChannel = null;
    140. }
    141. Debug.Log($"mpegFile.Length={wav.Length},ChannelCount={ChannelCount},lenSample={LeftChannel.Length}");
    142. }
    143. }

    NLayer

    1. // NLayer
    2. // file => mp3 byte[] => {MpegFile} => {ReadSamples} => sample float[] => AudioClip
    3. // 重点是ReadSamples方法的使用
    4. using NLayer;
    5. AudioClip LoadMp3Audio(byte[] audioBytes, string fileName)
    6. {
    7. System.IO.Stream memStream = new System.IO.MemoryStream(audioBytes);
    8. var mpegFile = new MpegFile(memStream);
    9. int lengthSamples = (int)(mpegFile.Length / sizeof(float) / mpegFile.Channels);
    10. float[] samples = new float[lengthSamples * mpegFile.Channels];
    11. int readCount = mpegFile.ReadSamples(samples, 0, lengthSamples * mpegFile.Channels);
    12. AudioClip ac = AudioClip.Create(fileName, lengthSamples, mpegFile.Channels, mpegFile.SampleRate, false);
    13. ac.SetData(samples, 0);
    14. return ac;
    15. }
    16. // wav加载同NAudio部分

    参考:

    1. https://www.jianshu.com/p/bf20e69f2cc4
    2. Audio - Import mp3 at runtime? - Unity Forum
    3. unity-load-mp3-at-runtime/Assets/NLayer at master · greggman/unity-load-mp3-at-runtime · GitHub
    4. (Solved)Load a 16bit .Wav file WITHOUT using WWW (at runtime) - Unity Forum

    改进加载效果:

    https://github.com/Cysharp/UniTask

  • 相关阅读:
    Android笔记:Android 组件化方案探索与思考
    使用Spring构建Web应用SpringMVC详解!
    Flutter中GetX系列五--Works(监听属性变化回调)使用详情
    通义千问, 文心一言, ChatGLM, GPT-4, Llama2, DevOps 能力评测
    【Java】博图S7通讯仿真测试上位机连接
    Fuzz测试 发现软件中的隐患和漏洞的秘密武器
    win10录屏功能怎么打开,详细图文教学,轻松学会
    中英文说明书丨Abbkine通用型免疫荧光工具箱(抗小鼠Dylight 488)
    C#/.NET/.NET Core优秀项目和框架精选(2023年10月更新,项目分类已整理完成欢迎大家踊跃提交PR一起完善让优秀的项目和框架不被埋没)
    Guava缓存及Guava线程池
  • 原文地址:https://blog.csdn.net/GrimRaider/article/details/126770083