]> git.sesse.net Git - nageru/commitdiff
Replace libebur128 with the R128 code from ebumeter.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 31 Oct 2015 23:57:13 +0000 (00:57 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 31 Oct 2015 23:57:13 +0000 (00:57 +0100)
__Much faster (something like 10x even ignoring the fact that libebur128
gets slower and slower the more data you add), and gives us the true
loudness range. We lose the oversampled peak detection for now, though.

Makefile
ebu_r128_proc.cc [new file with mode: 0644]
ebu_r128_proc.h [new file with mode: 0644]
mixer.cpp
mixer.h

index f20a3df6a6997575960da84b39eb2bc6c7ee9270..4c440240a6d65e6fe0c0d3236a440139fdcfc89f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,17 +1,19 @@
 CXX=g++
 PKG_MODULES = Qt5Core Qt5Gui Qt5Widgets Qt5OpenGLExtensions Qt5OpenGL libusb-1.0 movit lua5.2 libmicrohttpd
 CXXFLAGS := -O2 -march=native -g -std=gnu++11 -Wall -Wno-deprecated-declarations -Werror -fPIC $(shell pkg-config --cflags $(PKG_MODULES)) -pthread -DMOVIT_SHADER_DIR=\"$(shell pkg-config --variable=shaderdir movit)\"
-LDFLAGS=$(shell pkg-config --libs $(PKG_MODULES)) -lEGL -lGL -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lzita-resampler -lebur128
+LDFLAGS=$(shell pkg-config --libs $(PKG_MODULES)) -lEGL -lGL -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lzita-resampler
 
 # Qt objects
 OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o vu_common.o
 OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o
 
 # Mixer objects
-OBJS += h264encode.o mixer.o bmusb/bmusb.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampler.o httpd.o
+OBJS += h264encode.o mixer.o bmusb/bmusb.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampler.o httpd.o ebu_r128_proc.o
 
 %.o: %.cpp
        $(CXX) -MMD -MP $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $<
+%.o: %.cc
+       $(CXX) -MMD -MP $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $<
 
 %.h: %.ui
        uic $< -o $@
diff --git a/ebu_r128_proc.cc b/ebu_r128_proc.cc
new file mode 100644 (file)
index 0000000..d480ce0
--- /dev/null
@@ -0,0 +1,337 @@
+// ------------------------------------------------------------------------
+//
+//  Copyright (C) 2010-2011 Fons Adriaensen <fons@linuxaudio.org>
+//    
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+// ------------------------------------------------------------------------
+
+
+#include <string.h>
+#include <math.h>
+#include "ebu_r128_proc.h"
+
+
+float Ebu_r128_hist::_bin_power [100] = { 0.0f };
+float Ebu_r128_proc::_chan_gain [5] = { 1.0f, 1.0f, 1.0f, 1.41f, 1.41f };
+
+
+Ebu_r128_hist::Ebu_r128_hist (void)
+{
+    _histc = new int [751];
+    initstat ();
+    reset ();
+}
+
+
+Ebu_r128_hist::~Ebu_r128_hist (void)
+{
+    delete[] _histc;
+}
+
+
+void Ebu_r128_hist::reset (void)
+{
+    memset (_histc, 0, 751 * sizeof (float));
+    _count = 0;
+    _error = 0;
+}
+
+
+void Ebu_r128_hist::initstat (void)
+{
+    int i;
+
+    if (_bin_power [0]) return;
+    for (i = 0; i < 100; i++)
+    {
+       _bin_power [i] = powf (10.0f, i / 100.0f);
+    }
+}
+
+
+void Ebu_r128_hist::addpoint (float v)
+{
+    int k;
+
+    k = (int) floorf (10 * v + 700.5f);
+    if (k < 0) return;
+    if (k > 750)
+    {
+       k = 750;
+       _error++;
+    }
+    _histc [k]++;
+    _count++;
+}
+
+
+float Ebu_r128_hist::integrate (int i)
+{
+    int   j, k, n;
+    float s;
+
+    j = i % 100;
+    n = 0;
+    s = 0;
+    while (i <= 750)
+    {
+       k = _histc [i++];
+       n += k;
+       s += k * _bin_power [j++];
+       if (j == 100)
+       {
+           j = 0;
+           s /= 10.0f;
+       }
+    }  
+    return s / n;
+}
+
+
+void Ebu_r128_hist::calc_integ (float *vi, float *th)
+{
+    int   k;
+    float s;
+
+    if (_count < 50)
+    {
+        *vi = -200.0f;
+       return;
+    }
+    s = integrate (0);
+//  Original threshold was -8 dB below result of first integration
+//    if (th) *th = 10 * log10f (s) - 8.0f;
+//    k = (int)(floorf (100 * log10f (s) + 0.5f)) + 620;
+//  Threshold redefined to -10 dB below result of first integration
+    if (th) *th = 10 * log10f (s) - 10.0f;
+    k = (int)(floorf (100 * log10f (s) + 0.5f)) + 600;
+    if (k < 0) k = 0;
+    s = integrate (k);
+    *vi = 10 * log10f (s);
+}
+
+
+void Ebu_r128_hist::calc_range (float *v0, float *v1, float *th)
+{
+    int   i, j, k, n;
+    float a, b, s;
+
+    if (_count < 20)
+    {
+       *v0 = -200.0f;
+       *v1 = -200.0f;
+        return;
+    }
+    s = integrate (0);
+    if (th) *th = 10 * log10f (s) - 20.0f;
+    k = (int)(floorf (100 * log10f (s) + 0.5)) + 500;
+    if (k < 0) k = 0;
+    for (i = k, n = 0; i <= 750; i++) n += _histc [i]; 
+    a = 0.10f * n;
+    b = 0.95f * n;
+    for (i =   k, s = 0; s < a; i++) s += _histc [i];
+    for (j = 750, s = n; s > b; j--) s -= _histc [j];
+    *v0 = (i - 701) / 10.0f;
+    *v1 = (j - 699) / 10.0f;
+}
+
+
+
+
+Ebu_r128_proc::Ebu_r128_proc (void)
+{
+    reset ();
+}
+
+
+Ebu_r128_proc::~Ebu_r128_proc (void)
+{
+}
+
+
+void Ebu_r128_proc::init (int nchan, float fsamp)
+{
+    _nchan = nchan;
+    _fsamp = fsamp;
+    _fragm = (int) fsamp / 20;
+    detect_init (_fsamp);
+    reset ();
+}
+
+
+void Ebu_r128_proc::reset (void)
+{
+    _integr = false;
+    _frcnt = _fragm;
+    _frpwr = 1e-30f;
+    _wrind  = 0;
+    _div1 = 0;
+    _div2 = 0;
+    _loudness_M = -200.0f;
+    _loudness_S = -200.0f;
+    memset (_power, 0, 64 * sizeof (float));
+    integr_reset ();
+    detect_reset ();
+}
+
+
+void Ebu_r128_proc::integr_reset (void)
+{
+    _hist_M.reset ();
+    _hist_S.reset ();
+    _maxloudn_M = -200.0f;
+    _maxloudn_S = -200.0f;
+    _integrated = -200.0f;
+    _integ_thr  = -200.0f;
+    _range_min  = -200.0f;
+    _range_max  = -200.0f;
+    _range_thr  = -200.0f;
+    _div1 = _div2 = 0;
+}
+
+
+void Ebu_r128_proc::process (int nfram, float *input [])
+{
+    int  i, k;
+    
+    for (i = 0; i < _nchan; i++) _ipp [i] = input [i];
+    while (nfram)
+    {
+       k = (_frcnt < nfram) ? _frcnt : nfram;
+       _frpwr += detect_process (k);
+        _frcnt -= k;
+       if (_frcnt == 0)
+       {
+           _power [_wrind++] = _frpwr / _fragm;
+           _frcnt = _fragm;
+           _frpwr = 1e-30f;
+           _wrind &= 63;
+           _loudness_M = addfrags (8);
+           _loudness_S = addfrags (60);
+            if (_loudness_M > _maxloudn_M) _maxloudn_M = _loudness_M;
+            if (_loudness_S > _maxloudn_S) _maxloudn_S = _loudness_S;
+           if (_integr)
+           {
+               if (++_div1 == 2)
+               {
+                   _hist_M.addpoint (_loudness_M);
+                   _div1 = 0;
+               }
+               if (++_div2 == 10)
+               {
+                   _hist_S.addpoint (_loudness_S);
+                   _div2 = 0;
+                   _hist_M.calc_integ (&_integrated, &_integ_thr);
+                   _hist_S.calc_range (&_range_min, &_range_max, &_range_thr);
+               }
+           }
+       }
+       for (i = 0; i < _nchan; i++) _ipp [i] += k;
+       nfram -= k;
+    }
+}
+
+
+float Ebu_r128_proc::addfrags (int nfrag)
+{
+    int    i, k;
+    float  s;
+
+    s = 0;
+    k = (_wrind - nfrag) & 63;
+    for (i = 0; i < nfrag; i++) s += _power [(i + k) & 63];
+    return -0.6976f + 10 * log10f (s / nfrag);
+}
+
+
+void Ebu_r128_proc::detect_init (float fsamp)
+{
+    float a, b, c, d, r, u1, u2, w1, w2;
+
+    r = 1 / tan (4712.3890f / fsamp);
+    w1 = r / 1.12201f; 
+    w2 = r * 1.12201f;
+    u1 = u2 = 1.4085f + 210.0f / fsamp;
+    a = u1 * w1;
+    b = w1 * w1;
+    c = u2 * w2;
+    d = w2 * w2;
+    r = 1 + a + b;
+    _a0 = (1 + c + d) / r;
+    _a1 = (2 - 2 * d) / r;
+    _a2 = (1 - c + d) / r;
+    _b1 = (2 - 2 * b) / r;
+    _b2 = (1 - a + b) / r;
+    r = 48.0f / fsamp;
+    a = 4.9886075f * r;
+    b = 6.2298014f * r * r;
+    r = 1 + a + b;
+    a *= 2 / r;
+    b *= 4 / r;
+    _c3 = a + b;
+    _c4 = b;
+    r = 1.004995f / r;
+    _a0 *= r;
+    _a1 *= r;
+    _a2 *= r;
+}
+
+
+void Ebu_r128_proc::detect_reset (void)
+{
+    for (int i = 0; i < MAXCH; i++) _fst [i].reset ();
+}
+
+
+float Ebu_r128_proc::detect_process (int nfram)
+{
+    int   i, j;
+    float si, sj;
+    float x, y, z1, z2, z3, z4;
+    float *p;
+    Ebu_r128_fst *S;
+
+    si = 0;
+    for (i = 0, S = _fst; i < _nchan; i++, S++)
+    {
+       z1 = S->_z1;
+       z2 = S->_z2;
+       z3 = S->_z3;
+       z4 = S->_z4;
+       p = _ipp [i];
+       sj = 0;
+       for (j = 0; j < nfram; j++)
+       {
+           x = p [j] - _b1 * z1 - _b2 * z2 + 1e-15f;
+           y = _a0 * x + _a1 * z1 + _a2 * z2 - _c3 * z3 - _c4 * z4;
+           z2 = z1;
+           z1 = x;
+           z4 += z3;
+           z3 += y;
+           sj += y * y;
+       }
+       if (_nchan == 1) si = 2 * sj;
+       else si += _chan_gain [i] * sj;
+       S->_z1 = z1;
+       S->_z2 = z2;
+       S->_z3 = z3;
+       S->_z4 = z4;
+    }
+    return si;
+}
+
+
+
diff --git a/ebu_r128_proc.h b/ebu_r128_proc.h
new file mode 100644 (file)
index 0000000..dbecfcb
--- /dev/null
@@ -0,0 +1,136 @@
+// ------------------------------------------------------------------------
+//
+//  Copyright (C) 2010-2011 Fons Adriaensen <fons@linuxaudio.org>
+//    
+//  This program is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+// ------------------------------------------------------------------------
+
+
+#ifndef __EBU_R128_PROC_H
+#define __EBU_R128_PROC_H
+
+
+#define MAXCH 5
+
+
+class Ebu_r128_fst
+{
+private:
+
+    friend class Ebu_r128_proc;
+
+    void reset (void) { _z1 = _z2 = _z3 = _z4 = 0; }
+
+    float _z1, _z2, _z3, _z4;
+};
+
+
+class Ebu_r128_hist
+{
+private:
+
+    Ebu_r128_hist (void);
+    ~Ebu_r128_hist (void);
+
+    friend class Ebu_r128_proc;
+
+    void  reset (void);
+    void  initstat (void);
+    void  addpoint (float v);
+    float integrate (int ind);
+    void  calc_integ (float *vi, float *th);
+    void  calc_range (float *v0, float *v1, float *th);
+
+    int  *_histc;
+    int   _count;
+    int   _error;
+
+    static float _bin_power [100];
+};
+
+
+
+class Ebu_r128_proc
+{
+public:
+
+    Ebu_r128_proc (void);
+    ~Ebu_r128_proc (void);
+
+    void  init (int nchan, float fsamp);
+    void  reset (void);
+    void  process (int nfram, float *input []);
+    void  integr_reset (void);
+    void  integr_pause (void) { _integr = false; }
+    void  integr_start (void) { _integr = true; }
+
+    float loudness_M (void) const { return _loudness_M; }
+    float maxloudn_M (void) const { return _maxloudn_M; }
+    float loudness_S (void) const { return _loudness_S; }
+    float maxloudn_S (void) const { return _maxloudn_S; }
+    float integrated (void) const { return _integrated; }
+    float integ_thr (void) const { return _integ_thr; }
+    float range_min (void) const { return _range_min; }
+    float range_max (void) const { return _range_max; }
+    float range_thr (void) const { return _range_thr; }
+
+    const int *histogram_M (void) const { return _hist_M._histc; }
+    const int *histogram_S (void) const { return _hist_S._histc; }
+    int hist_M_count (void) const { return _hist_M._count; }
+    int hist_S_count (void) const { return _hist_S._count; }
+
+private:
+
+    float addfrags (int nfrag);
+    void  detect_init (float fsamp);
+    void  detect_reset (void);
+    float detect_process (int nfram);
+
+    bool              _integr;       // Integration on/off.
+    int               _nchan;        // Number of channels, 2 or 5.
+    float             _fsamp;        // Sample rate.
+    int               _fragm;        // Fragmenst size, 1/20 second.
+    int               _frcnt;        // Number of samples remaining in current fragment.
+    float             _frpwr;        // Power accumulated for current fragment.
+    float             _power [64];   // Array of fragment powers.
+    int               _wrind;        // Write index into _frpwr 
+    int               _div1;         // M period counter, 200 ms;
+    int               _div2;         // S period counter, 1s;
+    float             _loudness_M;
+    float             _maxloudn_M;
+    float             _loudness_S;
+    float             _maxloudn_S;
+    float             _integrated;
+    float             _integ_thr;
+    float             _range_min;
+    float             _range_max;
+    float             _range_thr;
+    
+    // Filter coefficients and states.
+    float             _a0, _a1, _a2;
+    float             _b1, _b2;
+    float             _c3, _c4;
+    float            *_ipp [MAXCH];
+    Ebu_r128_fst      _fst [MAXCH];
+    Ebu_r128_hist     _hist_M;
+    Ebu_r128_hist     _hist_S;
+
+    // Default channel gains.
+    static float      _chan_gain [5];
+};
+
+
+#endif
index 0d88f040329ab6fc4ff9dcf0bf7f6a893fdb3edd..e85710c25c519bfbd261c6ab6cf81cb2014f4796 100644 (file)
--- a/mixer.cpp
+++ b/mixer.cpp
@@ -147,7 +147,8 @@ Mixer::Mixer(const QSurfaceFormat &format)
                "} \n";
        cbcr_program_num = resource_pool->compile_glsl_program(cbcr_vert_shader, cbcr_frag_shader);
 
-       r128_state = ebur128_init(2, 48000, EBUR128_MODE_TRUE_PEAK | EBUR128_MODE_M | EBUR128_MODE_S | EBUR128_MODE_I | EBUR128_MODE_LRA);
+       r128.init(2, 48000);
+       r128.integr_start();
 }
 
 Mixer::~Mixer()
@@ -163,8 +164,6 @@ Mixer::~Mixer()
                }
                cards[card_index].usb->stop_dequeue_thread();
        }
-
-       ebur128_destroy(&r128_state);
 }
 
 namespace {
@@ -179,6 +178,30 @@ int unwrap_timecode(uint16_t current_wrapped, int last)
        }
 }
 
+float find_peak(const vector<float> &samples)
+{
+       float m = fabs(samples[0]);
+       for (size_t i = 1; i < samples.size(); ++i) {
+               m = std::max(m, fabs(samples[i]));
+       }
+       return m;
+}
+
+void deinterleave_samples(const vector<float> &in, vector<float> *out_l, vector<float> *out_r)
+{
+       size_t num_samples = in.size() / 2;
+       out_l->resize(num_samples);
+       out_r->resize(num_samples);
+
+       const float *inptr = in.data();
+       float *lptr = &(*out_l)[0];
+       float *rptr = &(*out_r)[0];
+       for (size_t i = 0; i < num_samples; ++i) {
+               *lptr++ = *inptr++;
+               *rptr++ = *inptr++;
+       }
+}
+
 }  // namespace
 
 void Mixer::bm_frame(int card_index, uint16_t timecode,
@@ -356,7 +379,11 @@ void Mixer::thread_func()
                                        }
                                }
                                if (card_index == 0) {
-                                       ebur128_add_frames_float(r128_state, samples_out.data(), samples_out.size() / 2);
+                                       vector<float> left, right;
+                                       peak = std::max(peak, find_peak(samples_out));
+                                       deinterleave_samples(samples_out, &left, &right);
+                                       float *ptrs[] = { left.data(), right.data() };
+                                       r128.process(left.size(), ptrs);
                                        h264_encoder->add_audio(pts_int, move(samples_out));
                                }
                        }
@@ -368,19 +395,12 @@ void Mixer::thread_func()
                }
 
                if (audio_level_callback != nullptr) {
-                       double loudness_s, loudness_i, peak_level_l, peak_level_r;
-                       double lra;
-                       ebur128_loudness_shortterm(r128_state, &loudness_s);
-                       ebur128_loudness_global(r128_state, &loudness_i);
-                       ebur128_loudness_range(r128_state, &lra);
-                       ebur128_true_peak(r128_state, 0, &peak_level_l);
-                       ebur128_true_peak(r128_state, 1, &peak_level_r);
-
-                       // FIXME: This is wrong. We need proper support from libebur128 for this.
-                       double loudness_range_low = loudness_i - 0.5 * lra;
-                       double loudness_range_high = loudness_i + 0.5 * lra;
-
-                       audio_level_callback(loudness_s, 20.0 * log10(max(peak_level_l, peak_level_r)),
+                       double loudness_s = r128.loudness_S();
+                       double loudness_i = r128.integrated();
+                       double loudness_range_low = r128.range_min();
+                       double loudness_range_high = r128.range_max();
+
+                       audio_level_callback(loudness_s, 20.0 * log10(peak),
                                             loudness_i, loudness_range_low, loudness_range_high);
                }
 
diff --git a/mixer.h b/mixer.h
index 498a2d13afd1a8e02340a455949daeb6c7cb8f9d..a94cf922fce2b0600521caef8ff87b01ff1939cb 100644 (file)
--- a/mixer.h
+++ b/mixer.h
@@ -7,7 +7,6 @@
 #undef Success
 #include <movit/effect_chain.h>
 #include <movit/flat_input.h>
-#include <ebur128.h>
 #include <functional>
 
 #include "bmusb/bmusb.h"
@@ -19,6 +18,7 @@
 #include "resampler.h"
 #include "timebase.h"
 #include "httpd.h"
+#include "ebu_r128_proc.h"
 
 #define NUM_CARDS 2
 
@@ -164,7 +164,10 @@ private:
        bool should_quit = false;
 
        audio_level_callback_t audio_level_callback = nullptr;
-       ebur128_state *r128_state = nullptr;
+       Ebu_r128_proc r128;
+
+       // TODO: Implement oversampled peak detection.
+       float peak = 0.0f;
 };
 
 extern Mixer *global_mixer;