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 $@
--- /dev/null
+// ------------------------------------------------------------------------
+//
+// 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;
+}
+
+
+
--- /dev/null
+// ------------------------------------------------------------------------
+//
+// 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
"} \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()
}
cards[card_index].usb->stop_dequeue_thread();
}
-
- ebur128_destroy(&r128_state);
}
namespace {
}
}
+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,
}
}
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));
}
}
}
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);
}
#undef Success
#include <movit/effect_chain.h>
#include <movit/flat_input.h>
-#include <ebur128.h>
#include <functional>
#include "bmusb/bmusb.h"
#include "resampler.h"
#include "timebase.h"
#include "httpd.h"
+#include "ebu_r128_proc.h"
#define NUM_CARDS 2
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;