您在评论中表示“我正在播放音频文件...我将其读取为 byte[],然后我需要通过将值放入 [-1,1] 范围内来标准化音频,然后我需要将该 byte[] 放回到播放音频播放器中"
我在这里做了一个很大的假设,但我猜测您收到的数据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.
这里重要的是要注意以下几点:
- 由于音频数据是 16 位,因此来自单个通道的单个样本是
short
(2 个字节),不是int
(4 个字节),值范围为 -32768 到 32767。
- 该数据位于小尾数法 http://en.wikipedia.org/wiki/Endianness表示,除非您的架构也是小端,否则您不能使用 .NETBitConverter http://msdn.microsoft.com/en-us/library/system.bitconverter.aspx用于转换的类。
- 我们不必将数据拆分为每个通道的流,因为我们根据任一通道的单个最高值对两个通道进行标准化。
- 将浮点值转换为整数值将导致量化错误,因此您可能需要使用某种抖动 http://en.wikipedia.org/wiki/Dither(这本身就是一个完整的主题)。
辅助函数
在我们开始实际的标准化之前,让我们通过编写几个辅助函数来使这变得更容易short
from a byte[]
反之亦然:
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;
}
正常化
这里应该做出一个重要的区别:音频标准化 http://en.wikipedia.org/wiki/Audio_normalization is not与统计标准化 http://en.wikipedia.org/wiki/Normalization_(statistics)。在这里,我们将对音频数据进行峰值归一化,将信号放大恒定量,使其峰值处于上限。为了对音频数据进行峰值归一化,我们首先找到最大值,从上限中减去它(对于 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
包含我们音频数据中的最大值。现在要执行实际的标准化,我们减去biggest
从 32767 获取一个与音频数据中最大声样本的峰值偏移相对应的值。接下来,我们将此偏移添加到每个音频样本,有效地增加每个样本的音量,直到我们最大声的样本达到峰值。
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];
}
我们就完成了!现在您可以发送output
字节数组,其中包含规范化的 PCM 数据,发送到音频播放器。
最后一点,请记住,此代码并不是最有效的;它是最有效的。您可以组合其中几个循环,并且您可能可以使用Buffer.BlockCopy()
用于数组复制,以及修改您的short
to byte[]
辅助函数将字节数组作为参数并将值直接复制到数组中。
我没有做任何这些事情,以便更容易地了解发生了什么。
正如我之前提到的,你应该绝对地阅读有关抖动的内容,因为它将极大地提高音频输出的质量。
我自己一直在从事一个音频项目,所以我通过一些尝试和错误弄清楚了这一切;我希望它能帮助某人。