规范化音频,如何将浮点数组转换为字节数组

本文关键字:数组 转换 字节 字节数 音频 规范化 | 更新日期: 2023-09-27 18:28:26

大家好,我正在播放音频文件。我将其读取为byte[]然后我需要通过将值放入 [-1,1] 范围内来规范音频。然后我想将每个浮点值放入一个byte[i]数组中,然后将该byte[]放回播放的音频播放器中。

我试过这个:

byte[] data = ar.ReadData();
byte[] temp=new byte[data.Length];
float biggest= 0; ;
for (int i = 0; i < data.Length; i++)
{
    if (data[i] > biggest)
    {
        biggest= data[i];
    }
}

这部分代码应该放例如 0.43 int byte[] 如果可能的话,我试过这个,但它不起作用:

for (int i = 0; i < data.Length; i++)
{
    temp = BitConverter.GetBytes(data[i] * (1 / biggest));
}

规范化音频,如何将浮点数组转换为字节数组

在评论中,您说">我正在播放音频文件...我将其读取为 byte[],然后我需要通过将值放入 [-1,1] 范围内来规范音频,然后我需要将该字节 [] 放回播放音频播放器">

我在这里做了一个很大的假设,但我猜你从ar.ReadData()接收的数据是一个 2 通道 16 位/44.1kHz PCM 数据的字节数组。(旁注:你使用的是Alvas.Audio库吗?(如果是这种情况,这里是如何做你想做的事。

背景

首先,一点背景。2 通道、16 位 PCM 数据流如下所示:

   byte | 01 02 | 03 04 | 05 06 | 07 08 | 09 10 | 11 12 | ...
channel |  Left | Right | Left  | Right | Left |  Right | ...
  frame |     First     |    Second     |     Third     | ...
 sample | 1st L | 1st R | 2nd L | 2nd R | 3rd L | 3rd R | ... etc.

这里重要的是要注意几点:

  1. 由于音频数据是 16 位,因此来自单个通道的单个样本是short(2 个字节(,而不是int(4 个字节(,其值在 -32768 到 32767 的范围内。
  2. 此数据采用小端表示形式,除非体系结构也是小端表示形式,否则不能使用 .NET BitConverter 类进行转换。
  3. 我们不必将数据拆分为每个通道的流,因为我们根据任一通道的单个最大值对两个通道进行规范化。
  4. 将浮点值转换为整数值将导致量化错误,因此您可能希望使用某种抖动(这本身就是一个完整的主题(。

帮助程序函数

在我们进入实际规范化之前,让我们通过编写几个帮助程序函数来从byte[]获取short,反之亦然:

short GetShortFromLittleEndianBytes(byte[] data, int startIndex)
{
    return (short)((data[startIndex + 1] << 8)
         | data[startIndex]);
}
byte[] GetLittleEndianBytesFromShort(short data)
{
    byte[] b = new byte[2];
    b[0] = (byte)data;
    b[1] = (byte)(data >> 8 & 0xFF);
    return b;
}

正常化

这里应该有一个重要的区别:音频归一化与统计归一化不同。在这里,我们将对音频数据执行峰值归一化,将信号放大一个恒定量,使其峰值处于上限。要对音频数据进行峰值归一化,我们首先找到最大值,从上限(对于 16 位 PCM 数据,这是 32767(中减去它以获得偏移量,然后将每个值增加此偏移量。

因此,要规范化我们的音频数据,请首先扫描它以找到峰值幅度:

byte[] input = ar.ReadData();  // the function you used above
float biggest = -32768F;
float sample;
for (int i = 0; i < input.Length; i += 2)
{
    sample = (float)GetShortFromLittleEndianBytes(input, i);
    if (sample > biggest) biggest = sample;
}

此时,biggest包含音频数据中的最大值。现在,为了执行实际的归一化,我们从 32767 中减去 biggest,得到一个值,该值对应于音频数据中最响亮样本峰值的偏移量。接下来,我们将此偏移添加到每个音频样本,有效地增加每个样本的音量,直到我们最响亮的样本达到峰值。

float offset = 32767 - biggest;
float[] data = new float[input.length / 2];
for (int i = 0; i < input.Length; i += 2)
{
    data[i / 2] = (float)GetShortFromLittleEndianBytes(input, i) + offset;
}

最后一步是将样本从浮点值转换为整数值,并将它们存储为小端序short s。

byte[] output = new byte[input.Length];
for (int i = 0; i < output.Length; i += 2)
{
    byte[] tmp = GetLittleEndianBytesFromShort(Convert.ToInt16(data[i / 2]));
    output[i] = tmp[0];
    output[i + 1] = tmp[1];
}

我们完成了!现在,您可以将包含规范化 PCM 数据的 output 字节数组发送到音频播放器。

最后,请记住,这段代码不是最有效的;你可以组合其中的几个循环,你可能会使用 Buffer.BlockCopy() 进行数组复制,以及修改你的short byte[]帮助函数,将字节数组作为参数并将值直接复制到数组中。我没有这样做,以便更容易看到正在发生的事情。

正如我之前提到的,您绝对应该阅读抖动,因为它将大大提高音频输出的质量。

我自己一直在做一个音频项目,所以我通过一些反复试验弄清楚了这一切;我希望它能帮助某个地方的某个人。

这有效:

float number = 0.43f;
byte[] array = BitConverter.GetBytes(number);

什么对你不起作用?

if (Math.Abs(sample) > biggest) biggest = sample;

我会将其更改为:

if (Math.Abs(sample) > biggest) biggest = Math.Abs(sample);

因为如果最大值为负数,则将所有值乘以负数。

你可以

像这样使用Buffer.BlockCopy

float[] floats = new float[] { 0.43f, 0.45f, 0.47f };
byte[] result = new byte[sizeof(float) * floats.Length];
Buffer.BlockCopy(floats, 0, result, 0, result.Length);

您可以将temp更改为字节数组列表,以避免一直覆盖它。

    byte[] data = new byte[] { 1, 3, 5, 7, 9 };  // sample data
    IList<byte[]> temp = new List<byte[]>(data.Length);
    float biggest = 0; ;
    for (int i = 0; i < data.Length; i++)
    {
        if (data[i] > biggest)
            biggest = data[i];
    }
    for (int i = 0; i < data.Length; i++)
    {
        temp.Add(BitConverter.GetBytes(data[i] * (1 / biggest)));
    }