将 mp3 字节数组转换为浮点数组以与 Unity 一起玩

Converting mp3 byte array to float array to play with Unity

所以我目前正在尝试从外部麦克风(在这种情况下实际上是在机器人上)获取音频并将其流式传输到 Unity 以在场景中播放。我相当确定此音频以 mp3 格式编码,采样率为 16000 Hz,比特率为 192 kHz。

我可以在 Unity 中将此音频作为字节数组(似乎总是 Little Endian)获取,我想转换为浮点数组,每个值的范围从 -1.0f 到 + 1.0f 以便我可以使用 AudioClip.SetData 在 Unity 场景中播放它。我的问题是我目前无法做到这一点。

我的第一次尝试是基于这个 Whosebug 答案:create AudioClip from byte[] 它使用以下函数进行转换:

private float[] ConvertByteToFloat(byte[] array) {
        float[] floatArr = new float[array.Length / 4];
        for (int i = 0; i < floatArr.Length; i++) {
            if (BitConverter.IsLittleEndian) {
                Array.Reverse(array, i * 4, 4);
            }
            floatArr[i] = BitConverter.ToSingle(array, i * 4) / 0x80000000;
        }
        return floatArr;
    }

然后我这样调用它:

scaledAudio = ConvertByteToFloat(audioData);
AudioClip audioClip = AudioClip.Create("RobotAudio", scaledAudio.Length, 1, 16000, false);
audioClip.SetData(scaledAudio, 0);
AudioSource.PlayClipAtPoint(audioClip, robot.transform.position);

但结果是很多静态的,在记录一些输出时,我意识到我得到了一堆 NaN...

我在某处读到可以使用 BitConverter.ToInt16() 函数提取 mp3 音频,所以我相应地更改了 ConvertByteToFloat 函数,如下所示:

private float[] ConvertByteToFloat16(byte[] array) {
            float[] floatArr = new float[array.Length / 2];
            for (int i = 0; i < floatArr.Length; i++) {
                if (BitConverter.IsLittleEndian) {
                    Array.Reverse(array, i * 2, 2);
                }
                floatArr[i] = (float) (BitConverter.ToInt16(array, i * 2) / 32767f);
            }
            return floatArr;
        }

[注意:结果除以 32767f,因为我读到这是可能出现的最大值,我想将它缩小到 -1.0f 和 1.0f 之间]

从中得出的数字看起来更有希望。它们确实都在 -1.0f 和 1.0f 之间。但是当我尝试使用 Unity 播放音频时,我听到的都是静态的。

问题几乎肯定出在 byte[] 到 float[] 的转换上,但我可能在为 AudioClip 或 AudioSource 设置数据或播放器时犯了错误。

任何 help/suggestions 都非常感谢!

[补充资源:我进入unity的byte[]来自这里:https://github.com/ros-drivers/audio_common/blob/master/audio_capture/src/audio_capture.cpp 有一个相关的脚本可以获取这个捕获程序编码的数据并播放它(https://github.com/ros-drivers/audio_common/blob/master/audio_play/src/audio_play.cpp)。这工作得很好 - 所以如果我可以在那一秒 link 复制 audio_play 脚本的解码功能,看来我会很高兴!]

首先,仅当您的数据是 16 位 PCM 时,像这样将 byte[] 转换为 float[] 才有效。如果它是 16 位的。

如果你的音频确实是用MPEG-1/MPEG-2 Audio Layer 3 格式压缩的,那么进入流将不是简单的转换数据格式的问题,它需要解码(先压缩)。我会尝试让发件人生成标准的非编码 PCM 格式,您的代码应该开始工作

file you linked 中,它表示在设置过程中将数据编码为编码的 mp3 格式(左侧的行号)。

21 >> // Need to encoding or publish raw wave data
22 >> ros::param::param<std::string>("~format", _format, "mp3");

这意味着您有两个选择。

从您的媒体库中导出 Wave 格式(原始 PCM)

更改 C++ 库的输出格式以导出原始波形文件格式。

21 >> // Need to encoding or publish raw wave data
22 >> ros::param::param<std::string>("~format", _format, "wave");

通读代码,如果将第 22 行的第三个构造函数参数更改为 "wave",它会将数据导出为 .wav 格式,因此不需要解码在团结中。如果这是一个选项,这将要求您重新编译 C++ 代码。请注意,音频数据(波形格式)在内存中会略大(比 mp3)。

请参阅 audio_capture.cpp 文件的第 98 -> 109 行以了解检查 wave 或 mp3 格式的位置。

在 Unity 中解码 MP3 音频

否则你可以尝试在Unity中解码mp3数据。这很可能涉及使用 mp3 库(我发现的第一个库是 MP3Sharp). Otherwise there's a Unity asset called uAudio,它声明要进行实时 mp3 compression/decompression;这可能比使用通用的 mp3 解码器更简单,因为它已经设计好了对于 Unity。

我不建议自己编写 mp3 解码器,除非只是为了挑战或学习目的。


抛开所有想法,我的第一个尝试是使用上述 "wave" 参数重新编译您的 C++ 库!

希望对您有所帮助:)

  1. 下载此 .dll:https://www.dllme.com/dll/files/naudio_dll.html 并导入文件夹 Plugins

  2. 下载此 C# 脚本:https://www.dropbox.com/s/wks0ujanr0pm6nj/NAudioPlayer.cs?dl=0

  3. 下载并导入此 Unity Asset Store:https://assetstore.unity.com/packages/tools/gui/runtime-file-browser-113006

  4. 创建一个 C# 脚本,添加到一个游戏对象,并写入以下行:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using System.IO;
    using UnityEngine.UI;
    using System.Runtime;
    using System.Runtime.InteropServices;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Runtime.Serialization;
    using NAudio;
    using NAudio.Wave;
    using UnityEngine.Networking;
    using SimpleFileBrowser;

    public class ReadMp3 : MonoBehaviour{
    private AudioSource audioSource;
    public Text pathText;

    private void Start()
    {
        audioSource = GetComponent<AudioSource>();
    }
    public void ReadMp3Sounds()
    {
        FileBrowser.SetFilters(false, new FileBrowser.Filter("Sounds", ".mp3"));
        FileBrowser.SetDefaultFilter(".mp3");
        StartCoroutine(ShowLoadDialogCoroutine());
    }

    IEnumerator ShowLoadDialogCoroutine()
    {

        yield return FileBrowser.WaitForLoadDialog(false, null, "Select Sound", "Select");

        pathText.text = FileBrowser.Result;

        if (FileBrowser.Success)
        {
            byte[] SoundFile = FileBrowserHelpers.ReadBytesFromFile(FileBrowser.Result);
            yield return SoundFile;

            audioSource.clip = NAudioPlayer.FromMp3Data(SoundFile);
            audioSource.Play();   
        }
    }