WIP/RFC: completely rewritten channel remixing

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The current code in PulseAudio for channel remixing is quite complex, has a lot of ifs, and does not do the right thing. For example:

1) Given a "5.1 (Side)" sink input and "5.1" sink, it generates the rear channels as averages between Front and Side instead of just using Side.

2) The coefficients for downmixing rear channels from a 5.1 sink input to a stereo sink are very low. On compositions like "Lichtmond 2" it is quite annoying. AC3 specifies the -3 or -6 dB downmix level, which means dividing by 1.4 or by 2, not by 9.

3) The coefficient for downmixing Left channel into LFE (and thus the tonal balance) is different when playing stereo and 5.0 material on a 5.1 sink.

I found that I can get rid of a lot of complexity, while reproducing most (all?) of the good decisions that the old code did, by using a simple notion of "channel azimuth" and "channel proximity", and reordering the algorithm so that it first tries to use all input channels and then to fill unused output channels.

Each input channel is routed to one (or, if the situation is too symmetrical, two) output channels that are nearest to its position. Then, each unused output channel is filled in from one (or, in symmetrical cases, two) input channels that are the closest ones to its position. That's it, no need to explicitly consider special cases of exactly matching channels or mono layout. The old code was so complex because it did not know the word "nearest".

While at it, I also tried to fix cases (2) and (3). The resulting patch is attached. Yes, it is unreadable, as it typically happens with big rewrites.

Here is an example channel matrix for 5.1(Side) -> Stereo remapping with the new patch:

       I00   I01   I02   I03   I04   I05
    +------------------------------------
O00 | 0,385 0,000 0,192 0,192 0,231 0,000
O01 | 0,000 0,385 0,192 0,192 0,000 0,231

Here is 5.1(Side) -> 5.1:

       I00   I01   I02   I03   I04   I05
    +------------------------------------
O00 | 1,000 0,000 0,000 0,000 0,000 0,000
O01 | 0,000 1,000 0,000 0,000 0,000 0,000
O02 | 0,000 0,000 0,000 0,000 1,000 0,000
O03 | 0,000 0,000 0,000 0,000 0,000 1,000
O04 | 0,000 0,000 1,000 0,000 0,000 0,000
O05 | 0,000 0,000 0,000 1,000 0,000 0,000

Here is Stereo -> 5.1, with fill-all-channels enabled and LFE remixing disabled:

       I00   I01
    +------------
O00 | 1,000 0,000
O01 | 0,000 1,000
O02 | 0,600 0,000
O03 | 0,000 0,600
O04 | 0,500 0,500
O05 | 0,000 0,000

The patch is definitely not baked enough, e.g. it attenuates too much when LFE remixing is going on with 5.1 or 7.1 input. Also, there might be bugs and corner cases that I haven't thought about. Testing and criticizing is welcome, inclusion in the official tree is probably not a good idea.

The patch is an alternative approach to what was solved by https://lists.freedesktop.org/archives/pulseaudio-discuss/2018-October/030559.html , do not try to apply both.

--
Alexander E. Patrakov
From c26c3f2806fbf5ae200a2a34425a3e2a58c8533f Mon Sep 17 00:00:00 2001
From: "Alexander E. Patrakov" <patrakov@xxxxxxxxx>
Date: Sun, 14 Oct 2018 06:54:28 +0500
Subject: [PATCH] resampler: Completely rewritten channel remapping

The new implementation is based on geometrical considerations like
angles between the center channel and other channels, and the notion
of channel proximity.

It never does such stupidity as using front-left-of-center to fill
the rear-left channel if front-left is also present.

It also automatically deals with confusion between "5.1" and "5.1
(Side)" which is common in media players.

It is less lines of code than the old logic, and has much less special
cases.

It does not yield exactly the same result, that's intentional.

Signed-off-by: Alexander E. Patrakov <patrakov@xxxxxxxxx>
---
 src/pulsecore/resampler.c | 638 +++++++++++++++++---------------------
 1 file changed, 286 insertions(+), 352 deletions(-)

diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c
index d42cbc298..06bd1f93a 100644
--- a/src/pulsecore/resampler.c
+++ b/src/pulsecore/resampler.c
@@ -21,6 +21,7 @@
 #include <config.h>
 #endif
 
+#include <stdlib.h>
 #include <string.h>
 
 #include <pulse/xmalloc.h>
@@ -40,7 +41,7 @@ struct ffmpeg_data { /* data specific to ffmpeg */
 
 static int copy_init(pa_resampler *r);
 
-static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_remixed);
+static void setup_remap(const pa_resampler *r, pa_remap_t *m, unsigned crossover_freq, bool *lfe_remixed);
 static void free_remap(pa_remap_t *m);
 
 static int (* const init_table[])(pa_resampler *r) = {
@@ -320,7 +321,7 @@ pa_resampler* pa_resampler_new(
         const pa_channel_map *am,
         const pa_sample_spec *b,
         const pa_channel_map *bm,
-	unsigned crossover_freq,
+        unsigned crossover_freq,
         pa_resample_method_t method,
         pa_resample_flags_t flags) {
 
@@ -414,7 +415,7 @@ pa_resampler* pa_resampler_new(
 
     /* set up the remap structure */
     if (r->map_required)
-        setup_remap(r, &r->remap, &lfe_remixed);
+        setup_remap(r, &r->remap, crossover_freq, &lfe_remixed);
 
     if (lfe_remixed && crossover_freq > 0) {
         pa_sample_spec wss = r->o_ss;
@@ -713,153 +714,164 @@ pa_resample_method_t pa_parse_resample_method(const char *string) {
     return PA_RESAMPLER_INVALID;
 }
 
-static bool on_left(pa_channel_position_t p) {
+static const int channel_azimuth[PA_CHANNEL_POSITION_MAX] = {
+    [PA_CHANNEL_POSITION_FRONT_LEFT] = 30,
+    [PA_CHANNEL_POSITION_FRONT_RIGHT] = -30,
 
-    return
-        p == PA_CHANNEL_POSITION_FRONT_LEFT ||
-        p == PA_CHANNEL_POSITION_REAR_LEFT ||
-        p == PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER ||
-        p == PA_CHANNEL_POSITION_SIDE_LEFT ||
-        p == PA_CHANNEL_POSITION_TOP_FRONT_LEFT ||
-        p == PA_CHANNEL_POSITION_TOP_REAR_LEFT;
-}
+    [PA_CHANNEL_POSITION_REAR_CENTER] = 180,
+    [PA_CHANNEL_POSITION_REAR_LEFT] = 150,
+    [PA_CHANNEL_POSITION_REAR_RIGHT] = -150,
 
-static bool on_right(pa_channel_position_t p) {
+    [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = 15,
+    [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] -15,
 
-    return
-        p == PA_CHANNEL_POSITION_FRONT_RIGHT ||
-        p == PA_CHANNEL_POSITION_REAR_RIGHT ||
-        p == PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER ||
-        p == PA_CHANNEL_POSITION_SIDE_RIGHT ||
-        p == PA_CHANNEL_POSITION_TOP_FRONT_RIGHT ||
-        p == PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
-}
+    [PA_CHANNEL_POSITION_SIDE_LEFT] = 105,
+    [PA_CHANNEL_POSITION_SIDE_RIGHT] = -105,
 
-static bool on_center(pa_channel_position_t p) {
+    [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = 30,
+    [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = -30,
+    [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = 180,
+    [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = 150,
+    [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = -150,
 
-    return
-        p == PA_CHANNEL_POSITION_FRONT_CENTER ||
-        p == PA_CHANNEL_POSITION_REAR_CENTER ||
-        p == PA_CHANNEL_POSITION_TOP_CENTER ||
-        p == PA_CHANNEL_POSITION_TOP_FRONT_CENTER ||
-        p == PA_CHANNEL_POSITION_TOP_REAR_CENTER;
-}
+    /* everything else is either 0 or treated specially */
+};
 
-static bool on_lfe(pa_channel_position_t p) {
+static bool can_remix(pa_channel_position_t p) {
     return
-        p == PA_CHANNEL_POSITION_LFE;
+        p >= PA_CHANNEL_POSITION_FRONT_LEFT &&
+        p <= PA_CHANNEL_POSITION_SIDE_RIGHT &&
+        p != PA_CHANNEL_POSITION_LFE;
 }
 
-static bool on_front(pa_channel_position_t p) {
-    return
-        p == PA_CHANNEL_POSITION_FRONT_LEFT ||
-        p == PA_CHANNEL_POSITION_FRONT_RIGHT ||
-        p == PA_CHANNEL_POSITION_FRONT_CENTER ||
-        p == PA_CHANNEL_POSITION_TOP_FRONT_LEFT ||
-        p == PA_CHANNEL_POSITION_TOP_FRONT_RIGHT ||
-        p == PA_CHANNEL_POSITION_TOP_FRONT_CENTER ||
-        p == PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER ||
-        p == PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
+static int azimuth_to_side(int azimuth) {
+    if (azimuth == 0 || azimuth == 180)
+        return 0;
+    if (azimuth > 0)
+        return 1;
+    return -1;
 }
 
-static bool on_rear(pa_channel_position_t p) {
-    return
-        p == PA_CHANNEL_POSITION_REAR_LEFT ||
-        p == PA_CHANNEL_POSITION_REAR_RIGHT ||
-        p == PA_CHANNEL_POSITION_REAR_CENTER ||
-        p == PA_CHANNEL_POSITION_TOP_REAR_LEFT ||
-        p == PA_CHANNEL_POSITION_TOP_REAR_RIGHT ||
-        p == PA_CHANNEL_POSITION_TOP_REAR_CENTER;
-}
+static void get_nearest_channels(const pa_channel_map *map,
+                                 pa_channel_position_t needle,
+                                 pa_channel_position_t *result1,
+                                 pa_channel_position_t *result2) {
+    int i;
 
-static bool on_side(pa_channel_position_t p) {
-    return
-        p == PA_CHANNEL_POSITION_SIDE_LEFT ||
-        p == PA_CHANNEL_POSITION_SIDE_RIGHT ||
-        p == PA_CHANNEL_POSITION_TOP_CENTER;
-}
+    int azimuth_diff;
+    int min_azimuth_diff = 181;
 
-enum {
-    ON_FRONT,
-    ON_REAR,
-    ON_SIDE,
-    ON_OTHER
-};
+    pa_channel_position_t p;
+    int azimuth, side, p_azimuth, p_side;
+    int wanted_azimuth;
 
-static int front_rear_side(pa_channel_position_t p) {
-    if (on_front(p))
-        return ON_FRONT;
-    if (on_rear(p))
-        return ON_REAR;
-    if (on_side(p))
-        return ON_SIDE;
-    return ON_OTHER;
-}
+    *result1 = PA_CHANNEL_POSITION_INVALID;
+    *result2 = PA_CHANNEL_POSITION_INVALID;
 
-/* Fill a map of which output channels should get mono from input, not including
- * LFE output channels. (The LFE output channels are mapped separately.)
- */
-static void setup_oc_mono_map(const pa_resampler *r, float *oc_mono_map) {
-    unsigned oc;
-    unsigned n_oc;
-    bool found_oc_for_mono = false;
+    azimuth = channel_azimuth[needle];
+    if (needle == PA_CHANNEL_POSITION_TOP_CENTER)
+        azimuth = 106;  /* Nudge it to Side or Rear */
 
-    pa_assert(r);
-    pa_assert(oc_mono_map);
+    if (needle == PA_CHANNEL_POSITION_LFE)
+        azimuth = 29;   /* Nudge it to Left + Right, which are usually the big speakers */
 
-    n_oc = r->o_ss.channels;
+    side = azimuth_to_side(azimuth);
 
-    if (!(r->flags & PA_RESAMPLER_NO_FILL_SINK)) {
-        /* Mono goes to all non-LFE output channels and we're done. */
-        for (oc = 0; oc < n_oc; oc++)
-            oc_mono_map[oc] = on_lfe(r->o_cm.map[oc]) ? 0.0f : 1.0f;
-        return;
-    } else {
-        /* Initialize to all zero so we can select individual channels below. */
-        for (oc = 0; oc < n_oc; oc++)
-            oc_mono_map[oc] = 0.0f;
-    }
+    for (i = 0; i < map->channels; i++) {
+        p = map->map[i];
+        if (p == needle) {
+            /* Exact match */
+            *result1 = p;
+            return;
+        }
 
-    for (oc = 0; oc < n_oc; oc++) {
-        if (r->o_cm.map[oc] == PA_CHANNEL_POSITION_MONO) {
-            oc_mono_map[oc] = 1.0f;
-            found_oc_for_mono = true;
+        if (!can_remix(p)) {
+            /* Something like Top or LFE, we never fill it */
+            continue;
+        }
+
+        p_azimuth = channel_azimuth[p];
+        p_side = azimuth_to_side(p_azimuth);
+
+        if (side * p_side < 0) {
+            /* Left/right mismatch, skip */
+            continue;
         }
+
+        azimuth_diff = (540 + p_azimuth - azimuth) % 360 - 180;
+
+        if (abs(azimuth_diff) < abs(min_azimuth_diff)) {
+            *result1 = p;
+            min_azimuth_diff = azimuth_diff;
+        }
+
     }
-    if (found_oc_for_mono)
+
+    if (min_azimuth_diff == 0) {
+        /* The needle is exactly on or above the found channel, nothing more to mix in */
         return;
+    }
 
-    for (oc = 0; oc < n_oc; oc++) {
-        if (r->o_cm.map[oc] == PA_CHANNEL_POSITION_FRONT_CENTER) {
-            oc_mono_map[oc] = 1.0f;
-            found_oc_for_mono = true;
-        }
+    if (needle == PA_CHANNEL_POSITION_TOP_CENTER || needle == PA_CHANNEL_POSITION_LFE) {
+        azimuth = -azimuth;  /* Nudge it to the other side, symmetrically */
+        side = -1;
     }
-    if (found_oc_for_mono)
+
+    wanted_azimuth = azimuth - min_azimuth_diff;
+    if (wanted_azimuth > 180 || wanted_azimuth < -180) {
+        /* Wrong side */
         return;
+    }
 
-    for (oc = 0; oc < n_oc; oc++) {
-        if (r->o_cm.map[oc] == PA_CHANNEL_POSITION_FRONT_LEFT || r->o_cm.map[oc] == PA_CHANNEL_POSITION_FRONT_RIGHT) {
-            oc_mono_map[oc] = 1.0f;
-            found_oc_for_mono = true;
+    if (wanted_azimuth == -180)
+        wanted_azimuth = 180;
+
+    for (i = 0; i < map->channels; i++) {
+        p = map->map[i];
+
+        if (!can_remix(p)) {
+            /* Something like Top or LFE, we never fill it here */
+            continue;
+        }
+
+        p_azimuth = channel_azimuth[p];
+        if (p_azimuth != wanted_azimuth) {
+            /* Not a perfect symmetry */
+            continue;
+        }
+
+        /* At this point we found a channel that is as good as *result1,
+         * but from another side of the needle. Return either it or nothing.
+         */
+        p_side = azimuth_to_side(p_azimuth);
+        if (side * p_side >= 0) {
+            *result2 = p;
         }
-    }
-    if (found_oc_for_mono)
         return;
+    }
+}
 
-    /* Give up on finding a suitable map for mono, and just send it to all
-     * non-LFE output channels.
-     */
-    for (oc = 0; oc < n_oc; oc++)
-        oc_mono_map[oc] = on_lfe(r->o_cm.map[oc]) ? 0.0f : 1.0f;
+static float default_coupling(pa_channel_position_t p1, pa_channel_position_t p2) {
+    int a1 = abs(channel_azimuth[p1]);
+    int a2 = abs(channel_azimuth[p2]);
+
+    if (a1 < 90 && a2 > 90)
+        return 0.6;
+
+    if (a1 > 90 && a2 < 90)
+        return 0.6;
+
+    return 1.0;
 }
 
-static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_remixed) {
+static void setup_remap(const pa_resampler *r, pa_remap_t *m, unsigned crossover_freq, bool *lfe_remixed) {
     unsigned oc, ic;
     unsigned n_oc, n_ic;
-    bool ic_connected[PA_CHANNELS_MAX];
+    int ic_used[PA_CHANNELS_MAX];
+    bool oc_connected[PA_CHANNELS_MAX];
     pa_strbuf *s;
     char *t;
+    float max_sum;
 
     pa_assert(r);
     pa_assert(m);
@@ -875,24 +887,38 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_remixed)
     memset(m->map_table_f, 0, sizeof(m->map_table_f));
     memset(m->map_table_i, 0, sizeof(m->map_table_i));
 
-    memset(ic_connected, 0, sizeof(ic_connected));
+    memset(ic_used, 0, sizeof(ic_used));
+    memset(oc_connected, 0, sizeof(oc_connected));
+
     *lfe_remixed = false;
 
     if (r->flags & PA_RESAMPLER_NO_REMAP) {
         for (oc = 0; oc < PA_MIN(n_ic, n_oc); oc++)
             m->map_table_f[oc][oc] = 1.0f;
-
+        /* In this case, normalization is not needed */
     } else if (r->flags & PA_RESAMPLER_NO_REMIX) {
         for (oc = 0; oc < n_oc; oc++) {
             pa_channel_position_t b = r->o_cm.map[oc];
+            int ics = 0;
 
             for (ic = 0; ic < n_ic; ic++) {
                 pa_channel_position_t a = r->i_cm.map[ic];
 
-                /* We shall not do any remixing. Hence, just check by name */
-                if (a == b)
+                /* We shall not do any remixing. Hence, just check by name.
+                 * Still, there may be duplicates in either channel map. */
+                if (a == b) {
                     m->map_table_f[oc][ic] = 1.0f;
+                    ics++;
+                }
+            }
+
+            /* Normalize, because of possible duplicates */
+            if (ics > 1) {
+                for (ic = 0; ic < n_ic; ic++) {
+                    m->map_table_f[oc][ic] /= ics;
+                }
             }
+            /* No further normalization needed */
         }
     } else {
 
@@ -906,295 +932,203 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_remixed)
          * not identical to the original. The volume will not match, and the
          * two channels will be a linear combination of both.
          *
-         * This is loosely based on random suggestions found on the Internet,
-         * such as this:
-         * http://www.halfgaar.net/surround-sound-in-linux and the alsa upmix
-         * plugin.
-         *
          * The algorithm works basically like this:
          *
-         * 1) Connect all channels with matching names.
+         * 1) Attempt to use all source channels:
+         *    For each S: channel, finds one or two D: channels that are either
+         *    the exact match or geometricaly nearest to S:, not LFE, and not
+         *    Top. Mix it there with coefficient 1 or 0.6, depending on whether
+         *    the front/side line is crossed.
          *
-         * 2) Mono Handling:
-         *    S:Mono: See setup_oc_mono_map().
-         *    D:Mono: Avg all S:channels
+         * 2) If PA_RESAMPLER_NO_FILL_SINK is clear, fill the remaining
+         *    destination channels other than Top or LFE.
+         *    For each D: channel, find one or two nearest S: channels. Mix them
+         *    here with coefficient 1 or 0.6 depending on whether the
+         *    front/side line is crossed.
          *
-         * 3) Mix D:Left, D:Right (if PA_RESAMPLER_NO_FILL_SINK is clear):
-         *    D:Left: If not connected, avg all S:Left
-         *    D:Right: If not connected, avg all S:Right
+         *    Rationales:
          *
-         * 4) Mix D:Center (if PA_RESAMPLER_NO_FILL_SINK is clear):
-         *    If not connected, avg all S:Center
-         *    If still not connected, avg all S:Left, S:Right
+         *    MPV decodes standard AC3 and DTS tracks as "5.1 (side)", while
+         *    PulseAudio treats 5.1 speakers as just "5.1". They really mean
+         *    the same, so the channels must be simply remapped.
          *
-         * 5) Mix D:LFE
-         *    If not connected, avg all S:*
+         *    AC3 spec requires either -3 or -6 dB for rear -> front downmix,
+         *    so let's settle for -4.5 dB as something average.
          *
-         * 6) Make sure S:Left/S:Right is used: S:Left/S:Right: If not
-         *    connected, mix into all D:left and all D:right channels. Gain is
-         *    1/9.
+         *    AC3 spec requires -3, -4.5 or -6 dB (as indicated by the file)
+         *    as a center-to-front downmix coefficient. -6 dB is the easiest
+         *    to implement in general (in terms of not interfering with step
+         *    4), as it only requires to count how many times a given channel
+         *    is used.
          *
-         * 7) Make sure S:Center, S:LFE is used:
+         * 3) Mix D:LFE
+         *    If not connected, or the cut-off frequency is more than 240 Hz,
+         *    add the sum of all S:* divided by 2.
          *
-         *    S:Center, S:LFE: If not connected, mix into all D:left, all
-         *    D:right, all D:center channels. Gain is 0.5 for center and 0.375
-         *    for LFE. C-front is only mixed into L-front/R-front if available,
-         *    otherwise into all L/R channels. Similarly for C-rear.
+         *    Rationale: the balance between low and high frequencies must hold
+         *    the same no matter whether the user is listening to some stereo
+         *    music or to the same music with silent Rear channels added.
          *
-         * 8) Normalize each row in the matrix such that the sum for each row is
-         *    not larger than 1.0 in order to avoid clipping.
+         *    Films come with the LFE channel for traditional acoustics, not
+         *    for laptop speakers, thus the LFE channel needs to be remixed,
+         *    even if it already exists.
+         *
+         * 4) Normalize each column so that if a source channel is mixed into
+         *    many destinatopn ones, it does not overwhelm the other channel
+         *    which is mixed into only one destination.
+         *
+         * 5) Normalize the whole matrix such that the sum for each row except
+         *    D:LFE is not larger than 1.0 in order to avoid clipping.
+         *
+         *    (Intention, not implemented!)
+         *    D:LFE should be permitted to clip because otherwise we would need
+         *    to attenuate too much when remixing 7.0 -> 7.1. Clipping should
+         *    never happen in practice if the LFE channel is filtered properly.
+         *    However, if it is not filtered, clipping would be bad. Also,
+         *    currently, filtering is done after remixing, potentially in a
+         *    format like s16ne that does not allow extra amplitude for the
+         *    intermediate result :(
          *
-         * S: and D: shall relate to the source resp. destination channels.
          *
-         * Rationale: 1, 2 are probably obvious. For 3: this copies front to
-         * rear if needed. For 4: we try to find some suitable C source for C,
-         * if we don't find any, we avg L and R. For 5: LFE is mixed from all
-         * channels. For 6: the rear channels should not be dropped entirely,
-         * however have only minimal impact. For 7: movies usually encode
-         * speech on the center channel. Thus we have to make sure this channel
-         * is distributed to L and R if not available in the output. Also, LFE
-         * is used to achieve a greater dynamic range, and thus we should try
-         * to do our best to pass it to L+R.
+         * S: and D: shall relate to the source resp. destination channels.
          */
 
-        unsigned
-            ic_left = 0,
-            ic_right = 0,
-            ic_center = 0,
-            ic_unconnected_left = 0,
-            ic_unconnected_right = 0,
-            ic_unconnected_center = 0,
-            ic_unconnected_lfe = 0;
-        bool ic_unconnected_center_mixed_in = 0;
-        float oc_mono_map[PA_CHANNELS_MAX];
 
+        /* 1) Attempt to use all source channels */
         for (ic = 0; ic < n_ic; ic++) {
-            if (on_left(r->i_cm.map[ic]))
-                ic_left++;
-            if (on_right(r->i_cm.map[ic]))
-                ic_right++;
-            if (on_center(r->i_cm.map[ic]))
-                ic_center++;
-        }
-
-        setup_oc_mono_map(r, oc_mono_map);
-
-        for (oc = 0; oc < n_oc; oc++) {
-            bool oc_connected = false;
-            pa_channel_position_t b = r->o_cm.map[oc];
+            pa_channel_position_t b1, b2;
+            pa_channel_position_t a = r->i_cm.map[ic];
 
-            for (ic = 0; ic < n_ic; ic++) {
-                pa_channel_position_t a = r->i_cm.map[ic];
+            get_nearest_channels(&r->o_cm, a, &b1, &b2);
 
-                if (a == b) {
-                    m->map_table_f[oc][ic] = 1.0f;
+            pa_log_debug("Routing %s to %s and %s",
+                                pa_channel_position_to_string(a),
+                                pa_channel_position_to_string(b1),
+                                pa_channel_position_to_string(b2));
+            /* The loop is needed if there are two identical channels in o_cm.map */
+            for (oc = 0; oc < n_oc; oc++) {
+                pa_channel_position_t b = r->o_cm.map[oc];
 
-                    oc_connected = true;
-                    ic_connected[ic] = true;
+                if (b == b1 || b == b2) {
+                    m->map_table_f[oc][ic] = default_coupling(a, b);
+                    oc_connected[oc] = true;
+                    ic_used[ic] += 2;
                 }
-                else if (a == PA_CHANNEL_POSITION_MONO && oc_mono_map[oc] > 0.0f) {
-                    m->map_table_f[oc][ic] = oc_mono_map[oc];
+            }
 
-                    oc_connected = true;
-                    ic_connected[ic] = true;
-                }
-                else if (b == PA_CHANNEL_POSITION_MONO) {
-                    m->map_table_f[oc][ic] = 1.0f / (float) n_ic;
+            if (!ic_used[ic]) {
+                /* Cannot send this input channel anywhere based on geometry.
+                 * So send it to everywhere except LFE.
+                 * This includes Anything -> Mono mixing.
+                 */
+                pa_log_debug("Channel %s not routed to anywhere, spreading to all non-LFE outputs", pa_channel_position_to_string(a));
+                for (oc = 0; oc < n_oc; oc++) {
+                    pa_channel_position_t b = r->o_cm.map[oc];
+                    if (b == PA_CHANNEL_POSITION_LFE)
+                        continue;
 
-                    oc_connected = true;
-                    ic_connected[ic] = true;
+                    m->map_table_f[oc][ic] = 1.0f;
+                    oc_connected[oc] = true;
+                    ic_used[ic] += 2;
                 }
             }
+        }
 
-            if (!oc_connected) {
-                /* Try to find matching input ports for this output port */
-
-                if (on_left(b) && !(r->flags & PA_RESAMPLER_NO_FILL_SINK)) {
-
-                    /* We are not connected and on the left side, let's
-                     * average all left side input channels. */
-
-                    if (ic_left > 0)
-                        for (ic = 0; ic < n_ic; ic++)
-                            if (on_left(r->i_cm.map[ic])) {
-                                m->map_table_f[oc][ic] = 1.0f / (float) ic_left;
-                                ic_connected[ic] = true;
-                            }
-
-                    /* We ignore the case where there is no left input channel.
-                     * Something is really wrong in this case anyway. */
-
-                } else if (on_right(b) && !(r->flags & PA_RESAMPLER_NO_FILL_SINK)) {
-
-                    /* We are not connected and on the right side, let's
-                     * average all right side input channels. */
-
-                    if (ic_right > 0)
-                        for (ic = 0; ic < n_ic; ic++)
-                            if (on_right(r->i_cm.map[ic])) {
-                                m->map_table_f[oc][ic] = 1.0f / (float) ic_right;
-                                ic_connected[ic] = true;
-                            }
-
-                    /* We ignore the case where there is no right input
-                     * channel. Something is really wrong in this case anyway.
-                     * */
-
-                } else if (on_center(b) && !(r->flags & PA_RESAMPLER_NO_FILL_SINK)) {
-
-                    if (ic_center > 0) {
+        /* 2) Attempt to fill the remaining destination channels */
+        if (!(r->flags & PA_RESAMPLER_NO_FILL_SINK)) {
+            for (oc = 0; oc < n_oc; oc++) {
+                pa_channel_position_t a1, a2;
+                pa_channel_position_t b = r->o_cm.map[oc];
+                int gain = 2;
 
-                        /* We are not connected and at the center. Let's average
-                         * all center input channels. */
+                if (oc_connected[oc])
+                    continue;
 
-                        for (ic = 0; ic < n_ic; ic++)
-                            if (on_center(r->i_cm.map[ic])) {
-                                m->map_table_f[oc][ic] = 1.0f / (float) ic_center;
-                                ic_connected[ic] = true;
-                            }
+                if (!can_remix(b))
+                    continue;  /* This catches LFE and Top */
 
-                    } else if (ic_left + ic_right > 0) {
+                get_nearest_channels(&r->i_cm, b, &a1, &a2);
+                pa_log_debug("Filling in %s from %s and %s",
+                        pa_channel_position_to_string(b),
+                        pa_channel_position_to_string(a1),
+                        pa_channel_position_to_string(a2));
 
-                        /* Hmm, no center channel around, let's synthesize it
-                         * by mixing L and R.*/
+                if (a2 != PA_CHANNEL_POSITION_INVALID)
+                    gain = 1;
 
-                        for (ic = 0; ic < n_ic; ic++)
-                            if (on_left(r->i_cm.map[ic]) || on_right(r->i_cm.map[ic])) {
-                                m->map_table_f[oc][ic] = 1.0f / (float) (ic_left + ic_right);
-                                ic_connected[ic] = true;
-                            }
+                /* The loop is needed if there are two identical channels in i_cm.map */
+                for (ic = 0; ic < n_ic; ic++) {
+                    pa_channel_position_t a = r->i_cm.map[ic];
+                    if (a == a1 || a == a2) {
+                        m->map_table_f[oc][ic] = default_coupling(a, b) * gain / 2;
+                        oc_connected[oc] = true;
+                        ic_used[ic] += gain;
                     }
-
-                    /* We ignore the case where there is not even a left or
-                     * right input channel. Something is really wrong in this
-                     * case anyway. */
-
-                } else if (on_lfe(b) && !(r->flags & PA_RESAMPLER_NO_LFE)) {
-
-                    /* We are not connected and an LFE. Let's average all
-                     * channels for LFE. */
-
-                    for (ic = 0; ic < n_ic; ic++)
-                        m->map_table_f[oc][ic] = 1.0f / (float) n_ic;
-
-                    /* Please note that a channel connected to LFE doesn't
-                     * really count as connected. */
-
-                    *lfe_remixed = true;
                 }
             }
         }
 
-        for (ic = 0; ic < n_ic; ic++) {
-            pa_channel_position_t a = r->i_cm.map[ic];
-
-            if (ic_connected[ic])
-                continue;
-
-            if (on_left(a))
-                ic_unconnected_left++;
-            else if (on_right(a))
-                ic_unconnected_right++;
-            else if (on_center(a))
-                ic_unconnected_center++;
-            else if (on_lfe(a))
-                ic_unconnected_lfe++;
-        }
-
-        for (ic = 0; ic < n_ic; ic++) {
-            pa_channel_position_t a = r->i_cm.map[ic];
-
-            if (ic_connected[ic])
-                continue;
-
+        /* 3) Mix D:LFE */
+        if (!(r->flags & PA_RESAMPLER_NO_LFE)) {
             for (oc = 0; oc < n_oc; oc++) {
                 pa_channel_position_t b = r->o_cm.map[oc];
-
-                if (on_left(a) && on_left(b))
-                    m->map_table_f[oc][ic] = (1.f/9.f) / (float) ic_unconnected_left;
-
-                else if (on_right(a) && on_right(b))
-                    m->map_table_f[oc][ic] = (1.f/9.f) / (float) ic_unconnected_right;
-
-                else if (on_center(a) && on_center(b)) {
-                    m->map_table_f[oc][ic] = (1.f/9.f) / (float) ic_unconnected_center;
-                    ic_unconnected_center_mixed_in = true;
-
-                } else if (on_lfe(a) && !(r->flags & PA_RESAMPLER_NO_LFE))
-                    m->map_table_f[oc][ic] = .375f / (float) ic_unconnected_lfe;
-            }
-        }
-
-        if (ic_unconnected_center > 0 && !ic_unconnected_center_mixed_in) {
-            unsigned ncenter[PA_CHANNELS_MAX];
-            bool found_frs[PA_CHANNELS_MAX];
-
-            memset(ncenter, 0, sizeof(ncenter));
-            memset(found_frs, 0, sizeof(found_frs));
-
-            /* Hmm, as it appears there was no center channel we
-               could mix our center channel in. In this case, mix it into
-               left and right. Using .5 as the factor. */
-
-            for (ic = 0; ic < n_ic; ic++) {
-
-                if (ic_connected[ic])
+                if (b != PA_CHANNEL_POSITION_LFE)
                     continue;
 
-                if (!on_center(r->i_cm.map[ic]))
-                    continue;
+                if (oc_connected[oc] && crossover_freq < 240)
+                    break;
 
-                for (oc = 0; oc < n_oc; oc++) {
+                /* At this point, we are dealing with LFE output and know that we have
+                 * to remix it.
+                 */
 
-                    if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc]))
-                        continue;
+                for (ic = 0; ic < n_ic; ic++) {
+                    pa_channel_position_t a = r->i_cm.map[ic];
 
-                    if (front_rear_side(r->i_cm.map[ic]) == front_rear_side(r->o_cm.map[oc])) {
-                        found_frs[ic] = true;
-                        break;
+                    if (a == PA_CHANNEL_POSITION_LFE) {
+                        m->map_table_f[oc][ic] = 1.0f;
+                    } else {
+                        m->map_table_f[oc][ic] = 0.5f;
                     }
                 }
+                *lfe_remixed = true;
+            }
+        }
 
-                for (oc = 0; oc < n_oc; oc++) {
-
-                    if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc]))
-                        continue;
+        /* 4) Normalize each column */
+        for (ic = 0; ic < n_ic; ic++) {
+            int total_used = ic_used[ic];
 
-                    if (!found_frs[ic] || front_rear_side(r->i_cm.map[ic]) == front_rear_side(r->o_cm.map[oc]))
-                        ncenter[oc]++;
+            if (total_used > 0) {
+                for (oc = 0; oc < n_oc; oc++) {
+                    m->map_table_f[oc][ic] /= 0.5 * total_used;  /* Fixme: to some power != 1? */
                 }
             }
 
-            for (oc = 0; oc < n_oc; oc++) {
-
-                if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc]))
-                    continue;
-
-                if (ncenter[oc] <= 0)
-                    continue;
+        }
 
-                for (ic = 0; ic < n_ic; ic++) {
+        /* 5) Normalize the whole matrix */
+        max_sum = 0.25f;
+        for (oc = 0; oc < n_oc; oc++) {
+            float sum = 0.0;
+            /* XXX: do not consider LFE here when filtering once the
+             * storage of the intermediate result before filtering is
+             * modified to be able to represent large amplitudes. */
 
-                    if (!on_center(r->i_cm.map[ic]))
-                        continue;
+            for (ic = 0; ic < n_ic; ic++)
+                sum += m->map_table_f[oc][ic];
 
-                    if (!found_frs[ic] || front_rear_side(r->i_cm.map[ic]) == front_rear_side(r->o_cm.map[oc]))
-                        m->map_table_f[oc][ic] = .5f / (float) ncenter[oc];
-                }
+            if (sum > max_sum)
+                max_sum = sum;
+        }
+        pa_log_debug("Normalizing the whole matrix, dividing by %1.3f", max_sum);
+        for (oc = 0; oc < n_oc; oc++) {
+            for (ic = 0; ic < n_ic; ic++) {
+                m->map_table_f[oc][ic] /= max_sum;
             }
         }
     }
 
-    for (oc = 0; oc < n_oc; oc++) {
-        float sum = 0.0f;
-        for (ic = 0; ic < n_ic; ic++)
-            sum += m->map_table_f[oc][ic];
-
-        if (sum > 1.0f)
-            for (ic = 0; ic < n_ic; ic++)
-                m->map_table_f[oc][ic] /= sum;
-    }
-
     /* make an 16:16 int version of the matrix */
     for (oc = 0; oc < n_oc; oc++)
         for (ic = 0; ic < n_ic; ic++)
-- 
2.19.0

_______________________________________________
pulseaudio-discuss mailing list
pulseaudio-discuss@xxxxxxxxxxxxxxxxxxxxx
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss

[Index of Archives]     [Linux Audio Users]     [AMD Graphics]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux