从.WAV文件中读取24位样本

9
我知道如何从一个 .wav 文件中读取8位、16位和32位(PCM和浮点数)样本,因为(方便的是).Net Framework已经内置了这些精确大小的整数类型。但我不知道如何读取(并存储)24位(3字节)样本。
如何读取24位音频?也许我可以改变我的当前方法(下面)来解决我的问题吗?
private List<float> Read32BitSamples(FileStream stream, int sampleStartIndex, int sampleEndIndex)
{
    var samples = new List<float>();
    var bytes = ReadChannelBytes(stream, Channels.Left, sampleStartIndex, sampleEndIndex); // Reads bytes of a single channel.

    if (audioFormat == WavFormat.PCM)  // audioFormat determines whether to process sample bytes as PCM or floating point.
    {
        for (var i = 0; i < bytes.Length / 4; i++)
        {
            samples.Add(BitConverter.ToInt32(bytes, i * 4) / 2147483648f);
        }
    }
    else
    {
        for (var i = 0; i < bytes.Length / 4; i++)
        {
            samples.Add(BitConverter.ToSingle(bytes, i * 4));
        }
    }

    return samples;
}
1个回答

14

读取(并存储)24位采样非常简单。正如你所说的,该框架中不存在3字节整型,这意味着你只有两个选择;要么创建自己的类型,要么在样本的字节数组开头插入一个空字节(0),从而使它们成为32位采样(因此您可以使用int来存储/操作它们)。

我将解释并演示如何执行后者(在我看来也是更简单的方法)。


首先我们必须看一下 int 中如何存储 24位采样,

~ ~ ~ ~ ~ ~ ~ ~ ~ ~ 最高有效字节 ~ ~ 次高有效字节 ~ ~ 次低有效字节 ~ ~ 最低有效字节 ~ ~

24位采样: 11001101 01101001 01011100 00000000

32位采样: 11001101 01101001 01011100 00101001

MSB = 最高有效字节, LSB = 最低有效字节。

正如你所看到的,24位采样的LSB是0,因此你只需要声明一个长度为4个元素的byte[],然后将采样的3个字节读入该数组(从第1个元素开始),使得你的数组如下所示(实际上是向左移动了8位):

myArray[0]: 00000000

myArray[1]: 01011100

myArray[2]: 01101001

myArray[3]: 11001101

填满字节数组之后,你可以将其传递给 BitConverter.ToInt32(myArray, 0);,然后需要将采样向右移动8位才能以正确的24位整数表示形式获取采样(从-83886088388608);然后除以8388608以获得浮点值。

因此,将所有这些放在一起,你应该得到像这样的东西:

请注意,我编写以下代码时意图是“易于理解”,因此这不是最有效的方法;对于更快速的解决方案,请参见此代码下面的代码。

private List<float> Read24BitSamples(FileStream stream, int startIndex, int endIndex)
{
    var samples = new List<float>();
    var bytes = ReadChannelBytes(stream, Channels.Left, startIndex, endIndex);
    var temp = new List<byte>();
    var paddedBytes = new byte[bytes.Length / 3 * 4];
    
    // Right align our samples to 32-bit (effectively bit shifting 8 places to the left).
    for (var i = 0; i < bytes.Length; i += 3)
    {
        temp.Add(0);            // LSB
        temp.Add(bytes[i]);     // 2nd LSB
        temp.Add(bytes[i + 1]); // 2nd MSB
        temp.Add(bytes[i + 2]); // MSB
    }

    // BitConverter requires collection to be an array.
    paddedBytes = temp.ToArray();
    temp = null;
    bytes = null;

    for (var i = 0; i < paddedBytes.Length / 4; i++)
    {
        samples.Add(BitConverter.ToInt32(paddedBytes, i * 4) / 2147483648f); // Skip the bit shift and just divide, since our sample has been "shited" 8 places to the right we need to divide by 2147483648, not 8388608.
    }

    return samples;
}

为了实现更快的1效果,您可以改用以下方法:

private List<float> Read24BitSamples(FileStream stream, int startIndex, int endIndex)
{
    var bytes = ReadChannelBytes(stream, Channels.Left, startIndex, endIndex);
    var samples = new float[bytes.Length / 3];

    for (var i = 0; i < bytes.Length; i += 3)
    {
        samples[i / 3] = (bytes[i] << 8 | bytes[i + 1] << 16 | bytes[i + 2] << 24) / 2147483648f;
    }
    
    return samples.ToList();
}


1在将上述代码与先前方法进行基准测试后,此解决方案的速度大约快450%至550%。


3
你应该左对齐数据,即将零放在底部而不是顶部。这样做的一个原因是可以将符号位放置在正确的位置。根据你目前的回答,你将无法获得负数表示。第二个原因是当数据左对齐时,你可以使用相同的缩放因子(1.0/0x80000000)将其转换为浮点数,无论原始位深度为24位还是32位。 - jaket
1
@jaket 感谢您指出这一点,我忘记了符号位。我知道我可以计算浮点值并避免使用 dynamic,但对于这种特定情况,必须尽可能保留 SQ,因此转换为和从浮点数将只会使 THD+N 增加更多。 - Sam
你可以在不丢失任何数据的情况下进行double类型的转换。 - jaket
1
@jaket 我知道,但我需要执行计算,这需要整个“double”(或“float”)来存储样本的精确振幅,然后在将样本转换回其原始位深度时会导致量化误差。 - Sam
2
一个24位有符号整数的最大值是8388607,而不是8388608。 - trw
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接