如何正确将 16Bit 字节数组转换为音频剪辑数据?

Posted

技术标签:

【中文标题】如何正确将 16Bit 字节数组转换为音频剪辑数据?【英文标题】:How to convert 16Bit byte array to audio clip data correctly? 【发布时间】:2020-11-26 13:26:24 【问题描述】:

我与 Media Foundation 合作,我需要做的是将声音样本帧从字节转换为音频浮点数据。为了做到这一点,我使用了这样的方法(我在谷歌的某个地方找到了):

    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 i = 0;

        while (i < convertedSize)
        
            int 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;
    

我是这样使用的:

...
byte[] source = ...; // lenght 43776

... = Convert16BitByteArrayToAudioClipData(source , 0, 0);
...

看起来这个方法工作错误,因为如果我传递一个大小为 43776 的数组,结果在索引 while 循环中的索引 i = 21886 偏移值将是 offset = 43776 它会导致下一个方法出现异常

data[i] = (float)BitConverter.ToInt16(source /*43776*/, offset /*43776*/) / maxValue;

因为这些值不可能相同。

问题是 - 如何解决这个方法?或者也许有人可以建议改用什么?

编辑

    private static float[] Convert16BitByteArrayToAudioClipData(byte[] source)
    
        float[] data = new float[source.Length];

        for (int i = 0; i < source.Length; i++)
        
            data[i] = (float) source[i];
        

        return data;
    

【问题讨论】:

我认为音频文件有一个 ASCII 标头。用记事本打开。通常有一个像 0x01 这样的起始字符,音频开始的位置和大小。 您之前的问题是媒体基金会的 C++ 代码。你真的切换到 C# 了吗?代码 sn-p 对处理假定的 PCM 音频数据缓冲区没有任何意义。 @RomanR。我愿意。我与 Unity 合作。使用媒体基础,我解码文件以获得示例帧(在 android 上赢得 impl 我有另一个解码器),然后我将它们全部传递给 C# 端,因为 Unity Audio Player 用 C# 编写以支持跨平台 Media Foundation API 为您提供(假设 - 您的问题不包含有关有效音频数据格式的任何信息)有符号 16 位整数的紧凑数组。您附加的代码 sn-p 意外地从比特流中获取大小......这显然是错误的,您只需要一个一个地读取整数并将它们转换为浮点数。 @RomanR。编辑了我的问题。是你的意思吗? 【参考方案1】:

整数需要变成-1..+1个浮点值

    private static float[] Convert16BitByteArrayToAudioClipData(byte[] source)
    
        float[] data = new float[source.Length];

        for (int i = 0; i < source.Length; i++)
        
            data[i] = ((float) source[i] / Int16.MaxValue); // <<---
        

        return data;
    

【讨论】:

这是错误的。如果源是16 bit,则data 数组应该是source 数组长度的一半。此外,source[i] / Int16.MaxValue 会为负值生成稍微不正确的值。 @apocalypse 上面写的有一个有效方面(只有一个):如果这段代码突然对你有用,那么你的再见数组数据不是 16 位而是 8 位(也可能是可能,因为您至少在此代码中不进行格式检查)。如果字节包含 16 位值,那么您需要将 source[i] 更改为正确读取这些值并分别更新数组大小。您将不得不通过调试来发现这一点,因为您在问题中显示的内容本身是不够的。 刚刚我完成了最终的实现,结果我在背景中听到了很多噪音和声音。我试图将解码后的字节写入.wav文件并从win播放器播放,我听说没问题,所以我很确定问题可能出在转换方法中,但我不知道如何检查它,任何想法?谢谢 @apocalypse 你能建议解决它的可能方法吗?谢谢 如上所述,您可能需要BitConverter.ToInt16 来读取16 位值,而不是对字节数组使用运算符=【参考方案2】:

最终我是这样做的:

    public static float[] Convert16BitByteArrayToAudioClipData(byte[] source)
    
        int x = sizeof(Int16); 
        int convertedSize = source.Length / x;
        float[] data = new float[convertedSize];
        Int16 maxValue = Int16.MaxValue;

        for (int i = 0; i < convertedSize; i++)
        
            int offset = i * x;
            data[i] = (float)BitConverter.ToInt16(source, offset) / maxValue;
            ++i;
        

        return data;
    

【讨论】:

以上是关于如何正确将 16Bit 字节数组转换为音频剪辑数据?的主要内容,如果未能解决你的问题,请参考以下文章

将短数组从音频记录转换为字节数组而不降低音频质量?

将 32 位浮点音频转换为 16 位字节数组?

如何将音频剪辑转换为数组以执行 FFT?

如何将字节数组转换为音频文件?

如何将 Int16 音频样本的数据转换为浮点音频样本数组

如何将一个字节数组转换为两个长值?