В комментарии вы указали: " Я играю аудио файл ... Я читаю его как байт [], а затем мне нужно нормализовать звук, поместив значения в диапазон [-1,1], а затем янужно вставить этот байт [] обратно в воспроизводимый аудиоплеер"
Я делаю большое предположение, но я предполагаю, что данные, которые вы получаете от ar.ReadData()
, представляют собой байтовый массив из 2данные канала PCM 16-бит / 44,1 кГц.(примечание: используете ли вы библиотеку 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. - Эти данныев представлении little-endian , и если ваша архитектура также не является прямым порядком байтов, вы не можете использовать класс .NET
BitConverter
для преобразования. - Нам не нужно разбивать данные на потоки для каждого канала, потому что мы нормализуем оба канала на основе одного наибольшего значения из любого канала.
- Преобразование значения с плавающей запятой в целочисленное значение приведет кошибки квантования, так что вы, вероятно, захотите использовать какое-то дизеринг (что само по себе является целой темой).
вспомогательные функции
Прежде чем мыперейти к фактической нормализации, давайте сделаем это проще для себяНаписание пары вспомогательных функций для получения short
от 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;
}
Нормализация
Здесь следует сделать важное различие: audioнормализация равна , а не совпадает с статистическая нормализация .Здесь мы собираемся выполнить нормализацию пика для наших аудиоданных, усиливая сигнал на постоянную величину, чтобы его пик находился на верхнем пределе.Для пиковой нормализации аудиоданных мы сначала находим наибольшее значение, вычитаем его из верхнего предела (для 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;
}
Последний шаг - преобразование сэмплов из числа с плавающей точкой в целое число.значения и сохраняем их как little-endian 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
на byte[]
, чтобы принимать байтовый массив в качестве параметра и копировать значение непосредственно вмассив.Я не делал ничего подобного, чтобы было легче увидеть, что происходит.
И, как я уже говорил, вы должны абсолютно прочитать информацию о дизеринге, поскольку это значительноулучшить качество вашего аудио выхода.
Я сам работал над аудио-проектом, поэтому я решил все это методом проб и ошибок;Надеюсь, это кому-нибудь поможет.