'Problem with rms and dB values of discrete samples
I'm trying to sample pcm-data via the ALSA-project on my RaspberryPi 4 in c. Recording things works like a charm, but tampering with the samples themselves leaves me confused, especially since i already did the same on a different project (ESP32).
Consider "buffer" as an array of varying size per session (ALSA allocates differently every time) containing 32bit 44100Hz discrete audio samples stored as 8 bit values (int32_t cast needed). In order to get the dBFS value of a time stretch as big as one buffer i thought to square every sample, add them together, divide by the number of samples, get the sqrt, divide by the INT32_MAX value and pull the log10 from that, which is finally multiplied by 20. A standard rms and then dBFS calculation:
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);
But instead of reasonable values for a somewhat quiet room (open window) or a speaker right next to the mics I get non-changing really quiet values of around -134 dBFS. Yes, the gain is low, so -134 could be possible but what I understand even less is what happens when I print out variables sum and rms:
buffersize: 262144
sum = -61773
rms = -262146
-138 dBFS
How could they ever be negative? This is probably a classic c-issue which I can't see at the moment.
Again: writing the samples to a file results in a high quality but low gain wav-file (header needed). Any help? thanks.
Solution 1:[1]
sum
is a uint32_t
, but you are printing it with %d
, which is for int
. The resulting behavior is not defined by the C standard. A common result is for values with the high bit set to be printed as negative numbers, but other behaviors are possible too. A correct conversion specification for unsigned int
is %u
, but, for uint32_t
, you should include <inttypes.h>
and use fprintf(stderr, "%" PRIu32 "\n", sum);
.
Additionally, the squares and the summation may exceed what can be represented in a uint32_t
, resulting in wrapping modulo 232.
rms
is a double
, but you are also printing it with %d
, which is very wrong. Use %g
, %f
, or %e
, or some other conversion for double
, possibly with various modifiers to select formatting options.
With the int32_t
decibel
, %d
might work in some C implementations, but a proper method is fprintf(stderr, "%" PRId32 " dBFS\n", decibel);
.
Your compiler should have warned you of at least the double
format issue. Pay attention to compiler warnings and fix the problems they report. Preferably, escalate compiler warnings to errors with the -Werror
switch to GCC and Clang or the /WX
switch to MSVC.
The line int32_t* samples = (int32_t*)buffer;
may result in prohibited aliasing. Be very sure that the memory for buffer
is probably defined to allow it to be aliased as int32_t
. If it is not, the behavior is not defined by the C standard, and alternative techniques of accessing the buffer should be used, such as copying the data into an int32_t
object one at a time or into an array of int32_t
.
Do not use pow
to compute squares as it is wasteful (and introduces inaccuracies when other types are involved). For your types, use static inline uint32_t square(int32_t x) { return x*x; }
and call it as square(samples[i])
. If overflow is occurring, consider using int64_t
when computing the square and uint64_t
for the sum.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |