用于规范化音频的 Java 算法

2022-09-04 20:56:54

我正在尝试规范语音的音频文件。

具体来说,如果音频文件包含音量峰值,我试图将其调平,因此安静的部分更响亮,峰值更安静。

我对音频操作知之甚少,除了我从这项任务中学到的东西。另外,我的数学很弱,令人尴尬。

我做了一些研究,Xuggle网站提供了一个示例,显示使用以下代码减少体积:(此处为完整版):)

@Override
  public void onAudioSamples(IAudioSamplesEvent event)
{
  // get the raw audio byes and adjust it's value 

  ShortBuffer buffer = event.getAudioSamples().getByteBuffer().asShortBuffer();
  for (int i = 0; i < buffer.limit(); ++i)
    buffer.put(i, (short)(buffer.get(i) * mVolume));

  super.onAudioSamples(event);
}

在这里,他们用常量修改中的字节。getAudioSamples()mVolume

基于这种方法,我尝试了规范化,将字节修改为规范化值,考虑文件中的max/min。(有关详细信息,请参阅下文)。我有一个简单的过滤器来保持“沉默”(即,任何低于某个值的东西)。getAudioSamples()

我发现输出文件非常嘈杂(即质量严重下降)。我假设错误要么在我的归一化算法中,要么在纵字节的方式中。但是,我不确定下一步该去哪里。

以下是我目前正在做的事情的删节版本。

步骤 1:查找文件中的峰值:

读取完整的音频文件,并查找所有音频采样的最高值和最低值buffer.get()

    @Override
    public void onAudioSamples(IAudioSamplesEvent event) {
        IAudioSamples audioSamples = event.getAudioSamples();
        ShortBuffer buffer = 
           audioSamples.getByteBuffer().asShortBuffer();

        short min = Short.MAX_VALUE;
        short max = Short.MIN_VALUE;
        for (int i = 0; i < buffer.limit(); ++i) {
            short value = buffer.get(i);
            min = (short) Math.min(min, value);
            max = (short) Math.max(max, value);
        }
        // assign of min/max ommitted for brevity.
        super.onAudioSamples(event);

    }

步骤 2:规范化所有值:

在类似于 step1 的循环中,将缓冲区替换为规范化值,调用:

    buffer.put(i, normalize(buffer.get(i));

public short normalize(short value) {
    if (isBackgroundNoise(value))
        return value;

    short rawMin = // min from step1
    short rawMax = // max from step1
    short targetRangeMin = 1000;
    short targetRangeMax = 8000;

    int abs = Math.abs(value);
    double a = (abs - rawMin) * (targetRangeMax - targetRangeMin);
    double b = (rawMax - rawMin);
    double result = targetRangeMin + ( a/b );

     // Copy the sign of value to result.
    result = Math.copySign(result,value);
    return (short) result;
}

问题:

  • 这是尝试规范化音频文件的有效方法吗?
  • 我的数学有效吗?normalize()
  • 为什么这会导致文件变得嘈杂,而演示代码中的类似方法则不会?

答案 1

我不认为“最小采样值”的概念很有意义,因为采样值只是代表了声波在特定时刻的当前“高度”。即,其绝对值将在音频剪辑的峰值和零之间变化。因此,似乎是错误的,可能会导致波形的某些失真。targetRangeMin

我认为更好的方法可能是使用某种权重函数,根据其大小减小样本值。即,较大的值比较小的值减少很大的百分比。这也会引入一些失真,但可能不是很明显。

编辑:这是这种方法的示例实现:

public short normalize(short value) {
    short rawMax = // max from step1
    short targetMax = 8000;

    //This is the maximum volume reduction
    double maxReduce = 1 - targetMax/(double)rawMax;

    int abs = Math.abs(value);
    double factor = (maxReduce * abs/(double)rawMax);

    return (short) Math.round((1 - factor) * value); 
}

作为参考,您的算法对振幅为10000的正弦曲线所做的:Original algorithm

这就解释了为什么标准化后的音频质量会变得更糟。

这是使用我建议的方法运行后的结果:normalizeSuggested algorithm


答案 2

音频的“规范化”是增加音频电平的过程,使得最大值等于某个给定值,通常是最大可能值。今天,在另一个问题中,有人解释了如何做到这一点(请参阅#1):音量标准化

但是,您继续说:“具体来说,音频文件包含音量峰值的地方,我试图将其调平,因此安静的部分更响亮,峰值更安静。这被称为“压缩”或“限制”(不要与压缩类型混淆,例如编码MP3时使用的压缩类型!您可以在此处阅读更多相关信息:http://en.wikipedia.org/wiki/Dynamic_range_compression

一个简单的压缩器并不是特别难以实现,但你说你的数学“令人尴尬地虚弱”。因此,您可能希望找到一个已经构建的。你也许能够找到一个用 http://sox.sourceforge.net/ 实现的压缩器,并将其从C转换为Java。据我所知,唯一一个可用的压缩器的java实现(而且不是很好)就是在这本书中。

作为解决问题的替代方法,您可能能够以每段 1/2 秒为单位对文件进行规范化,然后使用线性插值连接用于每段的增益值。您可以在此处阅读有关音频线性插值的信息:http://blog.bjornroche.com/2010/10/linear-interpolation-for-audio-in-c-c.html

我不知道源代码是否可用于调配器,但这是您可以尝试的其他方法。