我正在使用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;
}
答案 0 :(得分:7)
我想要做的是找出缓冲区中最大峰值并将其转换为分贝值。
从物理角度来看,这种方法没有意义。虽然可以指定与完整动态范围相关的单个样本值,但您可能对声级(即信号的功率)更感兴趣。一个单峰,即使它满量程也只带来很少的能量;由于谐波失真和带宽有限,它可能会产生非常大的爆音,但从技术上讲,它的功率密度分布在整个频段有限的频谱上。
你真正需要的是确定RMS值(均方根)。即。
RMS = sqrt( sum( square(samples) )/n_samples )
编辑: 请注意,上述内容仅适用于没有DC部分的信号。大多数模拟声音接口都是交流耦合的,所以这不是问题。但如果还有DC部分,则必须先从样本中减去 mean 值,即
RMS_DC_reject = sqrt( sum( square(samples - mean_sample) )/n_samples )
我将把它作为练习让读者将其添加到下面的代码中。 的
这为您提供了处理过的样本的 power ,这正是您真正想要的。你问过deciBels。现在我要问你dB(什么)?您需要参考值,因为Bels(或deciBels)是相对(即比较)度量。对于数字信号,满量程将为0 dB(FS),零线将为-20 log10( 2^B )
,其中B = sampling bit depth
。对于大约-96 dB(FS)的16位信号。
如果我们在谈论线路上的信号,公共参考是1 mW的功率,在这种情况下,标度是dB(m)。对于音频线路电平,已经定义满量程等于1 mW的信号功率,即1 V RMS在1 kOhm电阻上消耗的电压(再次有RMS)。
现在,由于我们的满量程由输入电路立即确定,输入电路以dB(m)为单位定义,您可以稍后将dB(FS)显示为dB(m)(或dBm)。
说到实际的声级,这取决于您的输入放大器增益和所用麦克风的转换效率。
据我所知,PA_SAMPLE_S16LE的幅度值应介于0和32768之间。但是我在分贝转换之前得到0到65536之间的值。我的转换有什么问题吗?
您询问了有符号整数格式。但是您将值转换为unsigned int。由于dB_FS相对于满量程,因此不要将其除以位数。对于16位的零信号,结果应该是大约-96dB。这种划分无论如何都没有意义,因为它只是将你的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) );
答案 1 :(得分:0)
使用连接非常类似于普通的read()和write() 系统调用。主要区别在于它们被称为 pa_simple_read()和pa_simple_write()。注意这些操作 永远阻止。
这似乎意味着返回值非常相似,因为在任何明智的地方似乎没有其他提及pa_simple_read的返回值。以下是opengroup's read() manual所说的内容:
成功完成后,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帮助我得出这个结论。