Нормализация звука, как преобразовать массив с плавающей точкой в ​​массив байтов? - PullRequest
3 голосов
/ 21 марта 2012

Привет всем, я играю аудио файл.Я читаю это как byte[], а затем мне нужно нормализовать звук, поместив значения в диапазон [-1,1].Затем я хочу поместить каждое значение с плавающей точкой в ​​массив byte[i] и затем поместить это byte[] обратно в воспроизводимый аудиоплеер.

Я пробовал это:

byte[] data = ar.ReadData();
byte[] temp=new byte[data.Length];
float biggest= 0; ;
for (int i = 0; i < data.Length; i++)
{
    if (data[i] > biggest)
    {
        biggest= data[i];
    }
}

Эта часть кода должна содержать, например, 0,43 int byte [], если это возможно, я пробовал это, но это не работает:

for (int i = 0; i < data.Length; i++)
{
    temp = BitConverter.GetBytes(data[i] * (1 / biggest));
}

Ответы [ 5 ]

15 голосов
/ 26 января 2013

В комментарии вы указали: " Я играю аудио файл ... Я читаю его как байт [], а затем мне нужно нормализовать звук, поместив значения в диапазон [-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.

Здесь важно принять к сведению несколько вещей:

  1. Поскольку аудиоданные 16-бит, одна выборка из одного канала представляет собой short (2 байта), а не int (4 байта), со значением в диапазоне от -32768 до 32767.
  2. Эти данныев представлении little-endian , и если ваша архитектура также не является прямым порядком байтов, вы не можете использовать класс .NET BitConverter для преобразования.
  3. Нам не нужно разбивать данные на потоки для каждого канала, потому что мы нормализуем оба канала на основе одного наибольшего значения из любого канала.
  4. Преобразование значения с плавающей запятой в целочисленное значение приведет кошибки квантования, так что вы, вероятно, захотите использовать какое-то дизеринг (что само по себе является целой темой).

вспомогательные функции

Прежде чем мыперейти к фактической нормализации, давайте сделаем это проще для себяНаписание пары вспомогательных функций для получения 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[], чтобы принимать байтовый массив в качестве параметра и копировать значение непосредственно вмассив.Я не делал ничего подобного, чтобы было легче увидеть, что происходит.

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

Я сам работал над аудио-проектом, поэтому я решил все это методом проб и ошибок;Надеюсь, это кому-нибудь поможет.

2 голосов
/ 21 марта 2012

Это работает:

float number = 0.43f;
byte[] array = BitConverter.GetBytes(number);

Что у вас не работает?

0 голосов
/ 17 марта 2013
if (Math.Abs(sample) > biggest) biggest = sample;

Я бы изменил это на:

if (Math.Abs(sample) > biggest) biggest = Math.Abs(sample);

Потому что, если наибольшее значение отрицательное, вы умножите все значения на отрицательное.

0 голосов
/ 21 марта 2012

Вы можете изменить temp на список байтовых массивов, чтобы избежать постоянной перезаписи.

    byte[] data = new byte[] { 1, 3, 5, 7, 9 };  // sample data
    IList<byte[]> temp = new List<byte[]>(data.Length);
    float biggest = 0; ;

    for (int i = 0; i < data.Length; i++)
    {
        if (data[i] > biggest)
            biggest = data[i];
    }

    for (int i = 0; i < data.Length; i++)
    {
        temp.Add(BitConverter.GetBytes(data[i] * (1 / biggest)));
    }
0 голосов
/ 21 марта 2012

Вы можете использовать Buffer.BlockCopy так:

float[] floats = new float[] { 0.43f, 0.45f, 0.47f };
byte[] result = new byte[sizeof(float) * floats.Length];
Buffer.BlockCopy(floats, 0, result, 0, result.Length);
...