jiixyj/libebur128

Incorrect results when measuring loudness per channel

kaleidoscope-dev opened this issue · 6 comments

Hi.

I have initiated 2 states. 1 per channel.

 for (int c=0;c<2;c++)
     {
      ebur128State[c] = new ebur128_state;
      ebur128State[c] = ebur128_init((unsigned) 1,(unsigned) freq,EBUR128_MODE_M |EBUR128_MODE_SAMPLE_PEAK);
      
      //ebur128_set_channel(ebur128State[c], c, c);
      //ebur128_set_channel(ebur128State[c], 0, 0);
     }

I then deinterleave the buffer into left and right int16_t arrays

int sampleCount = len/4;  // 2ch 2bytes per

int16_t *tmpLeft = new int16_t[sampleCount];
int16_t *tmpRight = new int16_t[sampleCount];

 int count = 0;
 for (int c=0;c<len;c+=4)
     {
      tmpLeft[count] = decode_signed16(&samples[c]);
      tmpRight[count] = decode_signed16(&samples[c+2]);
      count++;

     }

which I then feed to the algorithm and calculate momentary values for each channel.

ebur128_add_frames_short(ebur128State[0],tmpLeft,count);
ebur128_loudness_momentary(ebur128State[0], &momentary_l);

ebur128_add_frames_short(ebur128State[1],tmpRight,count);
ebur128_loudness_momentary(ebur128State[1], &momentary_r);

 if (momentary_l > max_momentary_l)
     max_momentary_l = momentary_l;

 if (momentary_r > max_momentary_r)
     max_momentary_r = momentary_r;

However, when feeding the buffer with audio data from "1kHz Sine -20 LUFS-16bit.wav" I get "-29.96LUFS" values on both channels instead of -20. The same thing happens when I feed the data from "1kHz Sine -26 LUFS-16bit.wav" the units are always -3dB lower.

Interestingly enough, I have a simple VU meter function I made that looks like this

 int sample_count = len / 4;   // 2 channels 2 bytes per channel
 int left_count = 0;
 int right_count = 0;
 int channel_offset = 0;
 double channel_sum = 0;
 double right_channel_sum = 0;

 for (int i=0;i<channels_;i++)  
     {
      for (int c=channel_offset;c<len;c+=4) // L[0][1] R[2][3] L[4] -- c+=4
          {
           int16_t rawSample = decode_signed16(&samples[c]);  
           double sampleValue  = rawSample / (double)(INT16_MAX);
           channel_sum += (sampleValue*sampleValue);
          }

      double rootMeanSquared = sqrt(channel_sum / sample_count);
      double logvalue = log10(rootMeanSquared);
      double decibel = 20 * logvalue;

      vChannelsOut[i] = decibel;

      channel_offset+=2;
      channel_sum  = 0;
     }

That give me the same output as the ebu128 function.

Any thoughts on what's happening here? I also have a question on what ebur128_set_channel does. Maybe this has to do something with it?

It is expected that the loudness for single channels is lower. You can think of it like this: For two channels, there are two physical loudspeakers. For a single channel, there is only one physical loudspeaker, so the total loudness is lower. I'm not sure if it's -3dB or -6dB or something else, I'd have to look it up...

That's also the reason for the EBUR128_DUAL_MONO flag for ebur128_set_channel. You can tell the library to count that mono channel "twice", as if it was played through two physical loudspeakers.

In general though, the loudness values you get for those single channels are only meaningful if you assume that this channel gets played back alone and on one speaker (or two in case of EBUR128_DUAL_MONO). I'm not sure if something like "per channel R128 loudness" has any meaning.

I think I get it now. Although I can get the per channel values, they won't really mean much as the loudness is calculated with the sum of all channels as per standard.
https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.1770-4-201510-I!!PDF-E.pdf pg. 3

I also have one more question.

double momentary;
double max_momentary = -HUGE_VAL;

int sampleCount = len/2;  // l and r

int16_t *tmp = new int16_t[sampleCount];

 int count = 0;
 for (int c=0;c<len;c+=2)
      tmp[count++] = decode_signed16(&samples[c]);


ebur128_add_frames_short(ebur128StateAllChannels,tmp,count);
ebur128_loudness_momentary(ebur128StateAllChannels, &momentary);


 if (momentary > max_momentary)
     max_momentary = momentary;

delete []tmp;


lfusOut_ = max_momentary;
return lfusOut_;

Here I'm just decoding a interleaved samples into a buffer and feed it to the ebur128_add_frames_short() function as this function accepts a int16_t array of samples. But I still get incorrect values in the output.

But if I just feed it the non processed buffer such as :

uint8_t* ebu_buffer  = NULL;
ebu_buffer = (uint8_t*) malloc(len);
memcpy(ebu_buffer,samples,len);
ebur128_add_frames_short(ebur128StateAllChannels,(short*)ebu_buffer,len/4);
ebur128_loudness_momentary(ebur128StateAllChannels, &momentary);

I get the correct -19.98 value on 1Khz -20 sine wave. So do I feed it just the buffer or array of shorts?

Your first example looks "more correct" to me when the ebur128_state is set up with a single channel. In that case, you must provide only data for one channel. Maybe it is just a coincidence that your second example gave -19.98 ?

In any case, to validate your results, you might want to give ffmpeg with the ebur128 filter a shot: https://ffmpeg.org/ffmpeg-filters.html#ebur128-1 There are a few examples here: https://ffmpeg.org/ffmpeg-filters.html#Examples-137 You can try to feed it a mono .wav file, for example, and see if it matches the values you get from the library.

This is my setup for the 1st example
...
Maybe I'm setting something up incorrectly.

Disregard that I was setting up 2 channels instead of 1. I'm now getting Correct -20.20 LUFS values for -20 sine wave and -26.60 for -26 since wave on the first example.
It's weird that I get nearly identical results with the 2nd one.

And since I'm already working with ffmpeg I'll definitely check out the ebur128 filter.

I'm very new at DSP and general audio programming and this was incredibly helpful for me.

Thanks.

Ah, if you are only feeding one channel into the ebur128_state you must initialize it with only one channel like this:

  ebur128StateAllChannels = ebur128_init((unsigned) 1,(unsigned) freq,EBUR128_MODE_M );

Then it should hopefully work.

Yeah. I was just editing my comment.

Anyway.
All works how. I'm closing this issue.
Thanks.