【问题标题】:Problem with rms and dB values of discrete samples离散样本的 rms 和 dB 值问题
【发布时间】:2021-04-25 14:10:16
【问题描述】:

我正在尝试通过我的 RaspberryPi 4 in c 上的 ALSA 项目对 pcm 数据进行采样。录制东西就像一种魅力,但篡改样本本身会让我感到困惑,特别是因为我已经在另一个项目 (ESP32) 上做过同样的事情。

将“缓冲区”视为每个会话大小不同的数组(ALSA 每次分配不同),其中包含存储为 8 位值的 32 位 44100Hz 离散音频样本(需要 int32_t 转换)。为了获得与一个缓冲区一样大的时间段的 dBFS 值,我想对每个样本进行平方,将它们加在一起,除以样本数,得到 sqrt,除以 INT32_MAX 值并从中提取 log10,最后乘以 20。标准 rms,然后是 dBFS 计算:

uint32_t sum = 0;
int32_t* samples = (int32_t*)buffer;
for(int i = 0; i < (size / (BIT_DEPTH/8)); i ++){

    sum += (uint32_t)pow(samples[i], 2);
}
double rms = sqrt(sum / (size / (BIT_DEPTH/8)));
int32_t decibel = (int32_t)(20 * log10(rms / INT32_MAX));
fprintf(stderr, "sum = %d\n", sum);
fprintf(stderr, "rms = %d\n", rms);
fprintf(stderr, "%d dBFS\n", decibel);

但是对于稍微安静的房间(打开的窗户)或麦克风旁边的扬声器,我得到的不是合理的值,而是大约 -134 dBFS 的不变的真正安静值。是的,增益很低,所以-134 是可能的,但我更不了解的是当我打印出变量 sum 和 rms 时会发生什么:

buffersize: 262144
sum = -61773
rms = -262146
-138 dBFS

他们怎么可能是消极的?这可能是我目前看不到的经典c-issue。

再次:将样本写入文件会产生高质量但低增益的 wav 文件(需要标头)。有什么帮助吗?谢谢。

【问题讨论】:

  • 你的 RPi 和 esp32 可以有不同的字节序吗?或者,如何从设备中采样/排序 32 位数据?
  • 以后在寻求调试帮助时,请在问题中提供minimal reproducible example
  • 只是一个小提示:在计算 dB_rms 时,您可以省略平方和的平方根,然后乘以 10 而不是 20。事实上,这样做可以更好地表示相对于基础物理而言,dB 意味着什么。
  • 好吧,要重现这一点,我需要包含整个 ALSA pcm-init,并且您需要准备好两个 i2s-MEMS-mics。这就是为什么我认为我应该保持简短并专注于有问题的领域,从答案来看,这完全取决于数学和 c 标准

标签: c audio raspberry-pi sampling pcm


【解决方案1】:

sum 是一个uint32_t,但您使用%d 打印它,它是用于int。产生的行为未由 C 标准定义。一个常见的结果是将设置为高位的值打印为负数,但其他行为也是可能的。 unsigned int 的正确转换规范是 %u,但对于 uint32_t,您应该包含 &lt;inttypes.h&gt; 并使用 fprintf(stderr, "%" PRIu32 "\n", sum);

此外,平方和总和可能会超过 uint32_t 中可以表示的值,从而导致模 232 包装。

rms 是一个double,但你也用%d 打印它,这是非常错误的。使用%g%f%e,或double 的其他转换,可能使用各种修饰符来选择格式选项。

使用int32)_tdecibel%d 可能适用于某些 C 实现,但正确的方法是 fprintf(stderr, "%" PRId32 " dBFS\n", decibel);

您的编译器至少应该警告您double 格式问题。注意编译器警告并修复它们报告的问题。最好使用-Werror 切换到 GCC 和 Clang 或 /WX 切换到 MSVC 将编译器警告升级为错误。

int32_t* samples = (int32_t*)buffer; 行可能会导致禁止别名。请确保buffer 的内存可能已定义为允许将其别名为int32_t。如果不是,则 C 标准未定义该行为,应使用访问缓冲区的替代技术,例如一次将数据复制到 int32_t 对象或 int32_t 数组中。

不要使用pow 来计算平方,因为它很浪费(并且在涉及其他类型时会引入不准确性)。对于您的类型,使用static inline uint32_t square(int32_t x) { return x*x; } 并将其称为square(samples[i])。如果发生溢出,请考虑在计算平方时使用int64_t,并在求和时使用uint64_t

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-26
    • 1970-01-01
    • 1970-01-01
    • 2022-01-16
    • 2019-02-04
    • 2022-09-26
    相关资源
    最近更新 更多