]> git.sesse.net Git - nageru/blobdiff - audio_mixer.cpp
Save another ~1% on the audio benchmark, by dealing with the std::deque more efficien...
[nageru] / audio_mixer.cpp
index afff6f32fe4cec9beac774c75b2782d5cba01f2d..59b5a70322e5cd249e0503ec7b11c31b0934868b 100644 (file)
@@ -6,6 +6,9 @@
 #include <stdio.h>
 #include <endian.h>
 #include <cmath>
+#ifdef __SSE__
+#include <immintrin.h>
+#endif
 
 #include "db.h"
 #include "flags.h"
@@ -77,7 +80,9 @@ void convert_fixed32_to_fp32(float *dst, size_t out_channel, size_t out_num_chan
        }
 }
 
-float find_peak(const float *samples, size_t num_samples)
+float find_peak_plain(const float *samples, size_t num_samples) __attribute__((unused));
+
+float find_peak_plain(const float *samples, size_t num_samples)
 {
        float m = fabs(samples[0]);
        for (size_t i = 1; i < num_samples; ++i) {
@@ -86,6 +91,54 @@ float find_peak(const float *samples, size_t num_samples)
        return m;
 }
 
+#ifdef __SSE__
+static inline float horizontal_max(__m128 m)
+{
+       __m128 tmp = _mm_shuffle_ps(m, m, _MM_SHUFFLE(1, 0, 3, 2));
+       m = _mm_max_ps(m, tmp);
+       tmp = _mm_shuffle_ps(m, m, _MM_SHUFFLE(2, 3, 0, 1));
+       m = _mm_max_ps(m, tmp);
+       return _mm_cvtss_f32(m);
+}
+
+float find_peak(const float *samples, size_t num_samples)
+{
+       const __m128 abs_mask = _mm_castsi128_ps(_mm_set1_epi32(0x7fffffffu));
+       __m128 m = _mm_setzero_ps();
+       for (size_t i = 0; i < (num_samples & ~3); i += 4) {
+               __m128 x = _mm_loadu_ps(samples + i);
+               x = _mm_and_ps(x, abs_mask);
+               m = _mm_max_ps(m, x);
+       }
+       float result = horizontal_max(m);
+
+       for (size_t i = (num_samples & ~3); i < num_samples; ++i) {
+               result = max(result, fabs(samples[i]));
+       }
+
+#if 0
+       // Self-test. We should be bit-exact the same.
+       float reference_result = find_peak_plain(samples, num_samples);
+       if (result != reference_result) {
+               fprintf(stderr, "Error: Peak is %f [%f %f %f %f]; should be %f.\n",
+                       result,
+                       _mm_cvtss_f32(_mm_shuffle_ps(m, m, _MM_SHUFFLE(0, 0, 0, 0))),
+                       _mm_cvtss_f32(_mm_shuffle_ps(m, m, _MM_SHUFFLE(1, 1, 1, 1))),
+                       _mm_cvtss_f32(_mm_shuffle_ps(m, m, _MM_SHUFFLE(2, 2, 2, 2))),
+                       _mm_cvtss_f32(_mm_shuffle_ps(m, m, _MM_SHUFFLE(3, 3, 3, 3))),
+                       reference_result);
+               abort();
+       }
+#endif
+       return result;
+}
+#else
+float find_peak(const float *samples, size_t num_samples)
+{
+       return find_peak_plain(samples, num_samples);
+}
+#endif
+
 void deinterleave_samples(const vector<float> &in, vector<float> *out_l, vector<float> *out_r)
 {
        size_t num_samples = in.size() / 2;
@@ -105,17 +158,19 @@ void deinterleave_samples(const vector<float> &in, vector<float> *out_l, vector<
 
 AudioMixer::AudioMixer(unsigned num_cards)
        : num_cards(num_cards),
-         level_compressor(OUTPUT_FREQUENCY),
          limiter(OUTPUT_FREQUENCY),
-         compressor(OUTPUT_FREQUENCY),
          correlation(OUTPUT_FREQUENCY)
 {
-       locut.init(FILTER_HPF, 2);
-
-       set_locut_enabled(global_flags.locut_enabled);
-       set_gain_staging_db(global_flags.initial_gain_staging_db);
-       set_gain_staging_auto(global_flags.gain_staging_auto);
-       set_compressor_enabled(global_flags.compressor_enabled);
+       for (unsigned bus_index = 0; bus_index < MAX_BUSES; ++bus_index) {
+               locut[bus_index].init(FILTER_HPF, 2);
+               locut_enabled[bus_index] = global_flags.locut_enabled;
+               gain_staging_db[bus_index] = global_flags.initial_gain_staging_db;
+               compressor[bus_index].reset(new StereoCompressor(OUTPUT_FREQUENCY));
+               compressor_threshold_dbfs[bus_index] = ref_level_dbfs - 12.0f;  // -12 dB.
+               compressor_enabled[bus_index] = global_flags.compressor_enabled;
+               level_compressor[bus_index].reset(new StereoCompressor(OUTPUT_FREQUENCY));
+               level_compressor_enabled[bus_index] = global_flags.gain_staging_auto;
+       }
        set_limiter_enabled(global_flags.limiter_enabled);
        set_final_makeup_gain_auto(global_flags.final_makeup_gain_auto);
 
@@ -185,7 +240,8 @@ void AudioMixer::reset_alsa_mutex_held(DeviceSpec device_spec)
        if (device->interesting_channels.empty()) {
                device->alsa_device.reset();
        } else {
-               device->alsa_device.reset(new ALSAInput(available_alsa_cards[card_index].address.c_str(), OUTPUT_FREQUENCY, 2, bind(&AudioMixer::add_audio, this, device_spec, _1, _2, _3, _4)));
+               const ALSAInput::Device &alsa_dev = available_alsa_cards[card_index];
+               device->alsa_device.reset(new ALSAInput(alsa_dev.address.c_str(), OUTPUT_FREQUENCY, alsa_dev.num_channels, bind(&AudioMixer::add_audio, this, device_spec, _1, _2, _3, _4)));
                device->capture_frequency = device->alsa_device->get_sample_rate();
                device->alsa_device->start_capture_thread();
        }
@@ -349,13 +405,64 @@ vector<float> AudioMixer::get_output(double pts, unsigned num_samples, Resamplin
                }
        }
 
-       // TODO: Move lo-cut etc. into each bus.
        vector<float> samples_out, left, right;
        samples_out.resize(num_samples * 2);
        samples_bus.resize(num_samples * 2);
        for (unsigned bus_index = 0; bus_index < input_mapping.buses.size(); ++bus_index) {
                fill_audio_bus(samples_card, input_mapping.buses[bus_index], num_samples, &samples_bus[0]);
 
+               // Cut away everything under 120 Hz (or whatever the cutoff is);
+               // we don't need it for voice, and it will reduce headroom
+               // and confuse the compressor. (In particular, any hums at 50 or 60 Hz
+               // should be dampened.)
+               if (locut_enabled[bus_index]) {
+                       locut[bus_index].render(samples_bus.data(), samples_bus.size() / 2, locut_cutoff_hz * 2.0 * M_PI / OUTPUT_FREQUENCY, 0.5f);
+               }
+
+               {
+                       lock_guard<mutex> lock(compressor_mutex);
+
+                       // Apply a level compressor to get the general level right.
+                       // Basically, if it's over about -40 dBFS, we squeeze it down to that level
+                       // (or more precisely, near it, since we don't use infinite ratio),
+                       // then apply a makeup gain to get it to -14 dBFS. -14 dBFS is, of course,
+                       // entirely arbitrary, but from practical tests with speech, it seems to
+                       // put ut around -23 LUFS, so it's a reasonable starting point for later use.
+                       if (level_compressor_enabled[bus_index]) {
+                               float threshold = 0.01f;   // -40 dBFS.
+                               float ratio = 20.0f;
+                               float attack_time = 0.5f;
+                               float release_time = 20.0f;
+                               float makeup_gain = from_db(ref_level_dbfs - (-40.0f));  // +26 dB.
+                               level_compressor[bus_index]->process(samples_bus.data(), samples_bus.size() / 2, threshold, ratio, attack_time, release_time, makeup_gain);
+                               gain_staging_db[bus_index] = to_db(level_compressor[bus_index]->get_attenuation() * makeup_gain);
+                       } else {
+                               // Just apply the gain we already had.
+                               float g = from_db(gain_staging_db[bus_index]);
+                               for (size_t i = 0; i < samples_bus.size(); ++i) {
+                                       samples_bus[i] *= g;
+                               }
+                       }
+
+#if 0
+                       printf("level=%f (%+5.2f dBFS) attenuation=%f (%+5.2f dB) end_result=%+5.2f dB\n",
+                               level_compressor.get_level(), to_db(level_compressor.get_level()),
+                               level_compressor.get_attenuation(), to_db(level_compressor.get_attenuation()),
+                               to_db(level_compressor.get_level() * level_compressor.get_attenuation() * makeup_gain));
+#endif
+
+                       // The real compressor.
+                       if (compressor_enabled[bus_index]) {
+                               float threshold = from_db(compressor_threshold_dbfs[bus_index]);
+                               float ratio = 20.0f;
+                               float attack_time = 0.005f;
+                               float release_time = 0.040f;
+                               float makeup_gain = 2.0f;  // +6 dB.
+                               compressor[bus_index]->process(samples_bus.data(), samples_bus.size() / 2, threshold, ratio, attack_time, release_time, makeup_gain);
+               //              compressor_att = compressor.get_attenuation();
+                       }
+               }
+
                // TODO: We should measure post-fader.
                deinterleave_samples(samples_bus, &left, &right);
                measure_bus_levels(bus_index, left, right);
@@ -372,61 +479,9 @@ vector<float> AudioMixer::get_output(double pts, unsigned num_samples, Resamplin
                }
        }
 
-       // Cut away everything under 120 Hz (or whatever the cutoff is);
-       // we don't need it for voice, and it will reduce headroom
-       // and confuse the compressor. (In particular, any hums at 50 or 60 Hz
-       // should be dampened.)
-       if (locut_enabled) {
-               locut.render(samples_out.data(), samples_out.size() / 2, locut_cutoff_hz * 2.0 * M_PI / OUTPUT_FREQUENCY, 0.5f);
-       }
-
        {
                lock_guard<mutex> lock(compressor_mutex);
 
-               // Apply a level compressor to get the general level right.
-               // Basically, if it's over about -40 dBFS, we squeeze it down to that level
-               // (or more precisely, near it, since we don't use infinite ratio),
-               // then apply a makeup gain to get it to -14 dBFS. -14 dBFS is, of course,
-               // entirely arbitrary, but from practical tests with speech, it seems to
-               // put ut around -23 LUFS, so it's a reasonable starting point for later use.
-               {
-                       if (level_compressor_enabled) {
-                               float threshold = 0.01f;   // -40 dBFS.
-                               float ratio = 20.0f;
-                               float attack_time = 0.5f;
-                               float release_time = 20.0f;
-                               float makeup_gain = from_db(ref_level_dbfs - (-40.0f));  // +26 dB.
-                               level_compressor.process(samples_out.data(), samples_out.size() / 2, threshold, ratio, attack_time, release_time, makeup_gain);
-                               gain_staging_db = to_db(level_compressor.get_attenuation() * makeup_gain);
-                       } else {
-                               // Just apply the gain we already had.
-                               float g = from_db(gain_staging_db);
-                               for (size_t i = 0; i < samples_out.size(); ++i) {
-                                       samples_out[i] *= g;
-                               }
-                       }
-               }
-
-       #if 0
-               printf("level=%f (%+5.2f dBFS) attenuation=%f (%+5.2f dB) end_result=%+5.2f dB\n",
-                       level_compressor.get_level(), to_db(level_compressor.get_level()),
-                       level_compressor.get_attenuation(), to_db(level_compressor.get_attenuation()),
-                       to_db(level_compressor.get_level() * level_compressor.get_attenuation() * makeup_gain));
-       #endif
-
-       //      float limiter_att, compressor_att;
-
-               // The real compressor.
-               if (compressor_enabled) {
-                       float threshold = from_db(compressor_threshold_dbfs);
-                       float ratio = 20.0f;
-                       float attack_time = 0.005f;
-                       float release_time = 0.040f;
-                       float makeup_gain = 2.0f;  // +6 dB.
-                       compressor.process(samples_out.data(), samples_out.size() / 2, threshold, ratio, attack_time, release_time, makeup_gain);
-       //              compressor_att = compressor.get_attenuation();
-               }
-
                // Finally a limiter at -4 dB (so, -10 dBFS) to take out the worst peaks only.
                // Note that since ratio is not infinite, we could go slightly higher than this.
                if (limiter_enabled) {
@@ -442,7 +497,8 @@ vector<float> AudioMixer::get_output(double pts, unsigned num_samples, Resamplin
        //      printf("limiter=%+5.1f  compressor=%+5.1f\n", to_db(limiter_att), to_db(compressor_att));
        }
 
-       // At this point, we are most likely close to +0 LU, but all of our
+       // At this point, we are most likely close to +0 LU (at least if the
+       // faders sum to 0 dB and the compressors are on), but all of our
        // measurements have been on raw sample values, not R128 values.
        // So we have a final makeup gain to get us to +0 LU; the gain
        // adjustments required should be relatively small, and also, the
@@ -552,15 +608,23 @@ void AudioMixer::send_audio_level_callback()
        double loudness_range_low = r128.range_min();
        double loudness_range_high = r128.range_max();
 
-       vector<float> bus_loudness;
-       bus_loudness.resize(input_mapping.buses.size());
-       for (unsigned bus_index = 0; bus_index < bus_r128.size(); ++bus_index) {
-               bus_loudness[bus_index] = bus_r128[bus_index]->loudness_S();
+       vector<BusLevel> bus_levels;
+       bus_levels.resize(input_mapping.buses.size());
+       {
+               lock_guard<mutex> lock(compressor_mutex);
+               for (unsigned bus_index = 0; bus_index < bus_r128.size(); ++bus_index) {
+                       bus_levels[bus_index].loudness_lufs = bus_r128[bus_index]->loudness_S();
+                       bus_levels[bus_index].gain_staging_db = gain_staging_db[bus_index];
+                       if (compressor_enabled[bus_index]) {
+                               bus_levels[bus_index].compressor_attenuation_db = -to_db(compressor[bus_index]->get_attenuation());
+                       } else {
+                               bus_levels[bus_index].compressor_attenuation_db = 0.0;
+                       }
+               }
        }
 
-       audio_level_callback(loudness_s, to_db(peak), bus_loudness,
+       audio_level_callback(loudness_s, to_db(peak), bus_levels,
                loudness_i, loudness_range_low, loudness_range_high,
-               gain_staging_db,
                to_db(final_makeup_gain),
                correlation.get_correlation());
 }