16-битный массив байтов с прямым порядком байтов в целочисленный массив в значение в децибелах

Я использую API PulseAudio, чтобы получить текущий вход микрофона в «реальном времени». Данные буфера доставляются в виде 16-битного массива байтов с прямым порядком байтов. Что я хотел бы сделать, так это узнать максимальный пиковый уровень в буфере и преобразовать его в значение в децибелах. Для этого мне нужно преобразовать каждые два значения массива байтов в одно целочисленное значение. В том же цикле я также ищу максимальное значение. После этого я конвертирую максимальное значение в децибел. Вот код C:

static ssize_t loop_write(int fd, const uint8_t *data, size_t size) 
{
int newsize = size / 2;
uint16_t max_value = 0;
int i = 0;

for (i = 0; i < size; i += 2)
{
    // put two bytes into one integer
    uint16_t val = data[i] + ((uint32_t)data[i+1] << 8);

    // find max value
    if(val > max_value)
       max_value = val;
}

// convert to decibel
float decibel = max_value / pow(2, 15);

if(decibel != 0)
    decibel = 20 * log(decibel);

// print result
printf("%f, ", decibel);

return size;
}

Насколько мне известно, значение амплитуды должно быть между 0 и 32768 для PA_SAMPLE_S16LE. Но я получаю значения от 0 до 65536 до преобразования децибел. Что-то не так с моей конверсией?

Для полноты картины я также публикую свою настройку pulseaudio:

int main(int argc, char*argv[]) 
{
char *device = "alsa_input.usb-041e_30d3_121023000184-00-U0x41e0x30d3.analog-mono";

// The sample type to use
static const pa_sample_spec ss = {
    .format = PA_SAMPLE_S16LE,
    .rate = 44100,
    .channels = 1
};
pa_simple *s = NULL;
int ret = 1;
int error;

// Create the recording stream 
if (!(s = pa_simple_new(NULL, argv[0], PA_STREAM_RECORD, device, "record", &ss, NULL, NULL, &error))) {
    fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
    goto finish;
}

for (;;) {
    uint8_t buf[BUFSIZE];

    // Record some data ...
    if (pa_simple_read(s, buf, sizeof(buf), &error) < 0) {
        fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(error));
        goto finish;
    }

    // And write it to STDOUT
    if (loop_write(STDOUT_FILENO, buf, sizeof(buf)) != sizeof(buf)) {
        fprintf(stderr, __FILE__": write() failed: %s\n", strerror(errno));
        goto finish;
    }
}

ret = 0;

finish:

if (s)
    pa_simple_free(s);

return 0;
}

person Dominik Schreiber    schedule 28.02.2013    source источник
comment
Я не знаю, понимаю ли я вас, но 16 бит имеют (беззнаковый) диапазон от 0 до 65535...   -  person bash.d    schedule 28.02.2013


Ответы (2)


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

С физической точки зрения такой подход не имеет смысла. Хотя можно указать отдельные значения выборки по отношению к полному динамическому диапазону, вас, вероятно, больше интересует уровень звука, то есть мощность сигнала. Отдельный пик, даже если он имеет полную шкалу, несет очень мало энергии; это может вызвать очень громкий хлопающий шум из-за гармонических искажений и ограниченной полосы пропускания, но технически его плотность мощности распределена по всему ограниченному спектру полосы.

Что вам действительно нужно, так это определить значение RMS (среднеквадратичное значение). т.е.

RMS = sqrt( sum( square(samples) )/n_samples )

EDIT: Обратите внимание, что приведенное выше верно только для сигналов без части постоянного тока. Большинство аналоговых звуковых интерфейсов связаны по переменному току, так что это не проблема. Но если есть еще и DC-часть, вы должны сначала вычесть среднее значение из выборок, т.е.

RMS_DC_reject = sqrt( sum( square(samples - mean_sample) )/n_samples )

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

Это дает вам мощность обрабатываемых сэмплов, что вам и нужно. Вы спрашивали о децибелах. Теперь я должен спросить вас, дБ(что)? Вам нужно эталонное значение, поскольку белы (или децибелы) являются относительными (то есть сравнительными) мерами. Для цифрового сигнала полная шкала будет равна 0 дБ (FS), а нулевая линия будет -20 log10( 2^B ), где B = sampling bit depth. Для 16-битного сигнала около -96 дБ (FS).

Если мы говорим о сигнале в линии, общепринятым эталоном является мощность 1 мВт, в этом случае масштаб составляет дБ(м). Для уровня звуковой линии было определено, что полная шкала равна 1 мВт мощности сигнала, то есть тому, что среднеквадратичное значение 1 В рассеивается на резисторе 1 кОм (снова среднеквадратичное значение).

Теперь, поскольку наша полная шкала сразу определяется входной схемой, которая определяется в единицах дБ(м), вы можете позднее отобразить дБ(FS) как дБ(м) (или дБм).

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


Насколько мне известно, значение амплитуды должно быть между 0 и 32768 для PA_SAMPLE_S16LE. Но я получаю значения от 0 до 65536 до преобразования децибел. Что-то не так с моей конверсией?

Вы спрашивали о целочисленном формате со знаком. Но вы вводите значения в беззнаковое целое число. А поскольку dB_FS относится к полной шкале, не делите ее на количество бит. Для нулевого сигнала 16 бит результат должен быть около -96 дБ. В любом случае деление не имеет смысла, так как оно просто масштабирует среднеквадратичное значение в диапазоне [0; 1], но log(0) расходится до -бесконечности. Отсюда ваше if заявление. Но помните, это физика, а физика непрерывна, здесь не должно быть оператора if.

Вы должны написать это так

// even for signed values this should be 2^N
// we're going to deal with signed later
double const MAX_SIGNAL = 1 << SAMPLE_BITS;

// using double here, because float offers only 25 bits of
// distortion free dynamic range.
double accum = 0;
int const n_samples = size/2;
for (i = 0; i < size; i += 2)
{
    // put two bytes into one __signed__ integer
    int16_t val = data[i] + ((int16_t)data[i+1] << 8);

    accum += val*val;
}
accum /= n_samples;

// Since we're using signed values we need to
// double the accumulation; of course this could be
// contracted into the statement above
accum *= 2.;

float const dB_FS = -20 * log10( MAX_SIGNAL - sqrt(accum) );
person datenwolf    schedule 28.02.2013
comment
Спасибо за исчерпывающий ответ/объяснение datenwolf! Я думаю, что db FS - это то, что я ищу. RMS измеряет воспринимаемую громкость для людей, верно? Но я явно ищу Clipping, потому что в будущем я хотел бы распознать, когда сигнал, поступающий в предусилитель, слишком горячий, и, если необходимо, уменьшить усиление предусилителя с помощью alsamixer, чтобы предотвратить дальнейшее клиппирование. - person Dominik Schreiber; 28.02.2013
comment
Если вы хотите найти отсечение, то не выполняйте преобразование дБ, просто проверьте, не выходят ли сэмплы за пределы диапазона значений. Однако, поскольку вашей конечной целью является автоматическая регулировка громкости, возьмите страницу из книги опытных звукоинженеров (под которой я подразумеваю все, что было написано до 1980 года): вы должны настроить усиление таким образом, чтобы среднеквадратичное значение сигнала составляло около -20 дБ. (FS) до -25 дБ (FS), это дает вам достаточный запас по уровню. Проблема с простым обнаружением клипа заключается в том, что вы просто не знаете, сколько мощности на самом деле несет сигнал, поэтому вам придется уменьшать усиление, клип за клипом; нехорошо. - person datenwolf; 28.02.2013
comment
@DominikSchreiber: Почему все до 1980 года? Потому что с 1980-х все стало цифровым, и начались войны за громкость, когда звукоинженеры начали использовать пиковую нормализацию, то есть масштабировать свой цифровой сигнал так, чтобы самый сильный пик был на всю шкалу. Самая первая мастеринговая станция CD-DA от Sony по-прежнему имела точный измеритель уровня около -20 дБ (FS), на который нужно было настроить среднеквадратичное значение сигнала. В более поздних версиях этого не было. - person datenwolf; 28.02.2013
comment
ок, воспользуюсь советом. Я думаю, что столкнулся с другой проблемой. При записи каждое значение dB_FS равно -96,329597. По вашему описанию это равнозначно отсутствию сигнала. Но это, вероятно, специфично для pulseaudio. Собираюсь открыть другую тему для этого. Еще раз спасибо за вашу помощь! - person Dominik Schreiber; 28.02.2013

Согласно простому API PulseAudio:

Использование соединения очень похоже на обычные системные вызовы read() и write(). Основное отличие состоит в том, что они называются pa_simple_read() и pa_simple_write(). Обратите внимание, что эти операции всегда блокируются.

Кажется, это означает, что возвращаемые значения очень похожи, так как нет никаких других упоминаний о возвращаемом значении pa_simple_read в каких-либо разумных местах. Вот что говорится в руководстве opengroup read():

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

Предположим, что pa_simple_read возвращает значение меньше sizeof buffer, ваша функция loop_write будет использовать неинициализированные значения. Это неопределенное поведение. Я предлагаю сохранить возвращаемое значение pa_simple_read и передать его в loop_write вместо sizeof(buf) после проверки на наличие ошибок.

Предположим, что значение, переданное в pa_simple_read, является нечетным числом, ваш loop_write будет использовать неинициализированное значение на последней итерации. Возможно, чтобы противостоять этому, вы могли бы изменить свой цикл на: for (i = 1; i < size; i += 2) и ваше объявление/инициализацию val на: uint16_t val = data[i-1] + ((uint32_t)data[i] << 8);

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

person autistic    schedule 28.02.2013
comment
-1 - OP говорит, что образцы представляют собой PA_SAMPLE_S16LE - подписанные 16-битные целые числа с прямым порядком байтов. На мой взгляд, хорошо задокументировано. - person mtrw; 28.02.2013
comment
Что я могу сделать, чтобы улучшить этот ответ? - person autistic; 28.02.2013
comment
@mtrw Какую часть этого ответа вы имеете в виду? Пожалуйста, покажите мне часть документации, в которой точно указано, сколько pa_simple_read будет выделять в буфер. - person autistic; 28.02.2013
comment
Код инициализации говорит использовать PA_SIMPLE_S16LE, поэтому я не понимаю, как pa_simple_read может возвращать нечетное количество байтов. - person mtrw; 28.02.2013
comment
@mtrw: Предположим, вы передаете массив байтов SIZE_MAX, будет ли pa_simple_read назначать байты SIZE_MAX этому массиву? Если нет, то как определить, сколько байт на самом деле назначено? - person autistic; 28.02.2013
comment
@mtrw: Предположим, что sizeof buffer равно 1. Будет ли pa_simple_read назначать 0 байтов, чтобы число оставалось четным? Предположим, что sizeof buffer — это какое-то другое нечетное число. Будет ли pa_simple_read назначать массиву только четное количество байтов? Где это задокументировано? - person autistic; 28.02.2013
comment
Ошибкой пользователя является указание буфера нечетного размера, если вы запросили 16-битные сэмплы, не так ли? Конечно, это может быть не задокументировано (хотя я довольно случайный пользователь PA, поэтому, возможно, я просто не сталкивался с этим), но это кажется достаточно ясным из контекста. - person mtrw; 28.02.2013
comment
@mtrw Это предположение. Программисты, которые делают предположения, а не полагаются на задокументированные факты, с большей вероятностью напишут код с ошибками. Пожалуйста, докажите мне, что pa_simple_read действительно отводит sizeof buffer байт в буфер. Если вы не можете окончательно заявить, что я не помогаю, то как вы можете оправдать отрицательный голос? - person autistic; 28.02.2013
comment
давайте продолжим это обсуждение в чате - person mtrw; 28.02.2013
comment
Спасибо вам, ребята! Я исправлю это. - person Dominik Schreiber; 28.02.2013