代码之家  ›  专栏  ›  技术社区  ›  Marty Pitt

用于规范音频的Java算法

  •  13
  • Marty Pitt  · 技术社区  · 13 年前

    我正在尝试使一个语音音频文件正常化。

    具体来说,当音频文件包含音量峰值时,我会尝试将其调平,这样安静的部分会更大,峰值也会更安静。

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

    我做了一些研究,Xuggle网站提供了一个示例,显示使用以下代码减少音量:( full version here )

    @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

    在这种方法的基础上,我尝试对中的字节进行规范化修改 获取音频样本() 考虑到文件中的最大值/最小值,将其转换为标准化值。(详见下文)。我有一个简单的过滤器,可以不使用“静音”(即任何低于值的内容)。

    我发现输出文件是 非常 嘈杂(即质量严重下降)。我认为错误要么是在我的规范化算法中,要么是我操作字节的方式。然而,我不确定下一步该去哪里。

    这是我目前正在做的事情的节略版本。

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

    读取完整的音频文件,并找到 buffer.get() 对于所有AudioSamples

        @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:规格化所有值:

    在类似于步骤1的循环中,用归一化值替换缓冲区,调用:

        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() 有效的
    • 为什么这会导致文件变得嘈杂,而演示代码中的类似方法却没有?
    2 回复  |  直到 13 年前
        1
  •  10
  •   Petter    13 年前

    我认为“最小样本值”的概念没有多大意义,因为样本值只是代表声波在某个时刻的当前“高度”。即其绝对值将在音频剪辑的峰值和零之间变化。因此,拥有 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

    这解释了为什么音频质量在被标准化之后变得更差。

    这是用我的建议跑步后的结果 normalize 方法: Suggested algorithm

        2
  •  5
  •   Community Mohan Dere    9 年前

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

    然而,你会继续说:“特别是,当音频文件包含音量峰值时,我正试图将其调平,这样安静的部分会更大,峰值也会更安静。”这被称为“压缩”或“限制”(不要与编码MP3时使用的压缩类型混淆!)。你可以在这里阅读更多信息: http://en.wikipedia.org/wiki/Dynamic_range_compression

    一个简单的压缩器并不特别难实现,但你说你的数学“弱得令人尴尬”。所以你可能想找到一个已经构建好的压缩器。您可能能够找到在中实现的压缩器 http://sox.sourceforge.net/ 并将其从C转换为Java。据我所知,压缩器的唯一java实现是谁的源代码可用(而且不是很好) this book

    作为解决问题的另一种方法,您可以将文件标准化为每段1/2秒的段,然后使用线性插值连接每个段的增益值。您可以在此处阅读有关音频线性插值的信息: http://blog.bjornroche.com/2010/10/linear-interpolation-for-audio-in-c-c.html

    我不知道源代码是否可用于 the levelator ,但这是你可以尝试的其他方法。