summaryrefslogtreecommitdiffstats
path: root/audio/chmap_sel.c
diff options
context:
space:
mode:
authorStefano Pigozzi <stefano.pigozzi@gmail.com>2014-12-28 09:38:22 +0100
committerStefano Pigozzi <stefano.pigozzi@gmail.com>2014-12-29 17:56:53 +0100
commit54aea7d5de13735bad291c0f1d9ba93b17a6321e (patch)
tree13ca20a462a71329f59f9463c0620df9a620ed70 /audio/chmap_sel.c
parent461ba50ed6e8227da034e4ebcb4316f34a720274 (diff)
downloadmpv-54aea7d5de13735bad291c0f1d9ba93b17a6321e.tar.bz2
mpv-54aea7d5de13735bad291c0f1d9ba93b17a6321e.tar.xz
chmap_sel: add multichannel fallback heuristic
Instead of just failing during channel map selection, try to select a close layout that makes most sense and upmix/downmix to that instead of failing AO initialization. The heuristic is rather simple, and uses the following steps: 1) If mono is required always prefer stereo to a multichannel upmix. 2) Search for an upmix that is an exact superset of the required channel map. 3) Search for a downmix that is the exact subset of the required channel map. 4) Search for either an upmix or downmix that is the closest (minimum difference of channels) to the required channel map.
Diffstat (limited to 'audio/chmap_sel.c')
-rw-r--r--audio/chmap_sel.c94
1 files changed, 94 insertions, 0 deletions
diff --git a/audio/chmap_sel.c b/audio/chmap_sel.c
index fe0b9598a0..23ebe71710 100644
--- a/audio/chmap_sel.c
+++ b/audio/chmap_sel.c
@@ -17,6 +17,7 @@
#include <stdlib.h>
#include <assert.h>
+#include <limits.h>
#include "common/common.h"
#include "chmap_sel.h"
@@ -183,6 +184,10 @@ bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map)
return true;
}
}
+
+ if (mp_chmap_sel_fallback(s, map))
+ return true;
+
// Fallback to mono/stereo as last resort
*map = (struct mp_chmap) MP_CHMAP_INIT_STEREO;
if (test_layout(s, map))
@@ -194,6 +199,95 @@ bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map)
return false;
}
+#define UPMIX_IDX 0
+#define DOWNMIX_IDX 1
+#define FALLBACK_IDX 2
+
+static bool test_fallbacks(struct mp_chmap *a, struct mp_chmap *b,
+ int best_diffs[2], struct mp_chmap best[2])
+{
+ struct mp_chmap diff1, diff2;
+
+ mp_chmap_diff(a, b, &diff1);
+ if (mp_chmap_contains(a, b) && best_diffs[UPMIX_IDX] > diff1.num) {
+ best[UPMIX_IDX] = *a;
+ best_diffs[UPMIX_IDX] = diff1.num;
+ return true;
+ }
+
+ mp_chmap_diff(b, a, &diff2);
+ if (mp_chmap_contains(b, a) && best_diffs[DOWNMIX_IDX] > diff2.num) {
+ best[DOWNMIX_IDX] = *a;
+ best_diffs[DOWNMIX_IDX] = diff2.num;
+ return true;
+ }
+
+ if (diff1.num > 0 && best_diffs[FALLBACK_IDX] > diff1.num) {
+ best[FALLBACK_IDX] = *a;
+ best_diffs[FALLBACK_IDX] = diff1.num;
+ return true;
+ }
+
+ if (diff2.num > 0 && best_diffs[FALLBACK_IDX] > diff2.num) {
+ best[FALLBACK_IDX] = *a;
+ best_diffs[FALLBACK_IDX] = diff2.num;
+ return true;
+ }
+
+ return false;
+}
+
+// Determine which channel map to fallback to given a source channel map. It
+// uses the following heuristic:
+// 1) If mono is required always prefer stereo to a multichannel upmix.
+// 2) Search for an upmix that is an exact superset of the required chmap.
+// 3) Search for a downmix that is the exact subset of the required chmap.
+// 4) Search for either an upmix or downmix that is the closest (minimum
+// difference of speakers) to the required chmap.
+bool mp_chmap_sel_fallback(const struct mp_chmap_sel *s, struct mp_chmap *map)
+{
+ // special case: if possible always fallback mono to stereo (instead of
+ // looking for a multichannel upmix)
+ struct mp_chmap mono = MP_CHMAP_INIT_MONO;
+ struct mp_chmap stereo = MP_CHMAP_INIT_STEREO;
+ if (mp_chmap_equals(&mono, map) && test_layout(s, &stereo)) {
+ *map = stereo;
+ return true;
+ }
+
+ int best_diffs[] = { INT_MAX, INT_MAX, INT_MAX };
+ struct mp_chmap best[] = { {0}, {0}, {0} };
+
+ for (int n = 0; n < s->num_chmaps; n++) {
+ struct mp_chmap e = s->chmaps[n];
+
+ if (mp_chmap_is_unknown(&e))
+ continue;
+
+ if (test_fallbacks(&e, map, best_diffs, best))
+ continue;
+
+ // in case we didn't match any fallback retry after replacing speakers
+ for (int i = 0; i < MP_ARRAY_SIZE(speaker_replacements); i++) {
+ struct mp_chmap t = e;
+ struct mp_chmap *r = (struct mp_chmap *)speaker_replacements[i];
+ if (replace_speakers(&t, r)) {
+ if (test_fallbacks(&t, map, best_diffs, best))
+ continue;
+ }
+ }
+ }
+
+ for (int i = UPMIX_IDX; i < MP_ARRAY_SIZE(best); i++) {
+ if (best_diffs[i] < INT_MAX) {
+ *map = best[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
// Set map to a default layout with num channels. Used for audio APIs that
// return a channel count as part of format negotiation, but give no
// information about the channel layout.