0

我正在使用 PulseAudio API 来“实时”获取当前的麦克风输入。缓冲区数据以 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;
}

据我所知,PA_SAMPLE_S16LE 的幅度值应该在 0 到 32768 之间。但在分贝转换之前,我得到的值在 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;
}
4

2 回答 2

7

我想做的是找出缓冲区中的最大峰值并将其转换为分贝值。

从物理的角度来看,这种方法没有意义。虽然可以指定与整个动态范围相关的单个样本值,但您可能对声级更感兴趣,即信号的功率。一个单一的峰值,即使它是满量程的,也只携带很少的能量;由于谐波失真和有限的带宽,它可能会导致非常响亮的爆裂噪声,但从技术上讲,它的功率密度分布在整个频带有限的频谱上。

您真正应该确定的是 RMS 值(均方根)。IE

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

编辑: 请注意,以上仅适用于没有直流部分的信号。大多数模拟声音接口都是交流耦合的,所以这不是问题。但如果也有直流部分,则必须先从样本中减去平均值,即

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

我将把它作为练习留给读者将其添加到下面的代码中。

这为您提供了处理样本的能力,这正是您真正想要的。你问了分贝。现在我要问你dB(什么)?您需要参考值,因为贝尔(或分贝)是相对(即比较)度量。对于数字信号,满量程为 0 dB(FS),零线为-20 log10( 2^B ),其中B = sampling bit depth。对于大约 -96 dB(FS) 的 16 位信号。

如果我们谈论线路上的信号,常见的参考是 1 mW 的功率,在这种情况下,比例是 dB(m)。对于音频线路电平,已定义满量程等于 1 mW 的信号功率,这是 1V RMS 在 1 kOhm 电阻上的耗散(这里又是 RMS)。

现在,由于我们的满量程立即由输入电路确定,输入电路以 dB(m) 为单位定义,因此您稍后可以将 dB(FS) 显示为 dB(m)(或 dBm)就好了。

当谈到实际的声级时,这取决于您的输入放大器增益和所用麦克风的转换效率。


据我所知,PA_SAMPLE_S16LE 的幅度值应该在 0 到 32768 之间。但在分贝转换之前,我得到的值在 0 到 65536 之间。我的转换有什么问题吗?

您询问了有符号整数格式。但是您将这些值转换为无符号整数。而且由于 dB_FS 是相对于满量程的,所以不要将它除以位数。对于 16 位的零信号,结果应该是大约 -96 dB。无论如何,除法没有任何意义,因为它只是将您的 RMS 缩放到范围 [0; 1],但 log(0) 发散到 -infinity。因此你的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) );
于 2013-02-28T12:14:41.173 回答
0

根据PulseAudio 简单 API

使用连接与普通的 read() 和 write() 系统调用非常相似。主要区别在于它们被称为 pa_simple_read() 和 pa_simple_write()。请注意,这些操作总是阻塞。

这似乎意味着返回值非常相似,因为在任何合理的地方似乎都没有提到 pa_simple_read 的返回值。这是opengroup 的 read() 手册所说的:

成功完成后,read() ... 应返回一个非负整数,指示实际读取的字节数。

假设 pa_simple_read 返回的值小于sizeof buffer,您的 loop_write 函数将使用未初始化的值。那是未定义的行为。sizeof(buf)我建议存储 pa_simple_read 的返回值,并在检查错误之后将其传递给 loop_write 。

假设传递给 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 帮助我得出这个结论。

于 2013-02-28T12:16:24.673 回答