Подтвердить что ты не робот

Java-алгоритм нормализации звука

Я пытаюсь нормализовать звуковой файл речи.

В частности, если аудиофайл содержит пики в объеме, я пытаюсь его выровнять, поэтому тихие разделы громче, а пики более тихие.

Я очень мало знаю об обработке аудио, помимо того, что я узнал от работы над этой задачей. Кроме того, моя математика неловко слаба.

Я провел некоторое исследование, и сайт Xuggle предоставляет образец, который показывает уменьшение объема, используя следующий код: (полная версия здесь)

@Override
  public void onAudioSamples(IAudioSamplesEvent event)
{
  // get the raw audio byes and adjust it 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.

Основываясь на этом подходе, я попытался нормализовать байты в getAudioSamples() до нормализованного значения, считая max/min в файле. (Подробнее см. Ниже). У меня есть простой фильтр, чтобы оставить "тишину" в одиночку (т.е. Что-нибудь ниже значения).

Я нахожу, что выходной файл очень шумный (т.е. качество серьезно ухудшается). Я предполагаю, что ошибка либо в моем алгоритме нормализации, либо в том, как я манипулирую байтами. Однако я не уверен, куда идти дальше.

Здесь сокращенная версия того, что я сейчас делаю.

Шаг 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() действительной?
  • Почему это может привести к тому, что файл станет шумным, где подобный подход в демо-коде не работает?
4b9b3361

Ответ 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

Это объясняет, почему качество звука ухудшается после нормализации.

Это результат после моего предлагаемого метода normalize: Suggested 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

Я не знаю, доступен ли исходный код для levelator, но что-то еще вы можете попробовать.