Coverage Report

Created: 2026-03-27 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/audio/chmap_sel.c
Line
Count
Source
1
/*
2
 * This file is part of mpv.
3
 *
4
 * mpv is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * mpv is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
#include <stdlib.h>
19
#include <assert.h>
20
#include <limits.h>
21
22
#include "common/common.h"
23
#include "common/msg.h"
24
#include "chmap_sel.h"
25
26
static const struct mp_chmap speaker_replacements[][2] = {
27
    // 5.1 <-> 5.1 (side)
28
    { MP_CHMAP2(SL, SR), MP_CHMAP2(BL, BR) },
29
    // 7.1 <-> 7.1 (rear ext)
30
    { MP_CHMAP2(SL, SR), MP_CHMAP2(SDL, SDR) },
31
};
32
33
// Try to replace speakers from the left of the list with the ones on the
34
// right, or the other way around.
35
static bool replace_speakers(struct mp_chmap *map, struct mp_chmap list[2])
36
5.43k
{
37
5.43k
    mp_assert(list[0].num == list[1].num);
38
5.43k
    if (!mp_chmap_is_valid(map))
39
0
        return false;
40
15.5k
    for (int dir = 0; dir < 2; dir++) {
41
10.6k
        int from = dir ? 0 : 1;
42
10.6k
        int to   = dir ? 1 : 0;
43
10.6k
        bool replaced = false;
44
10.6k
        struct mp_chmap t = *map;
45
209k
        for (int n = 0; n < t.num; n++) {
46
594k
            for (int i = 0; i < list[0].num; i++) {
47
397k
                if (t.speaker[n] == list[from].speaker[i]) {
48
1.68k
                    t.speaker[n] = list[to].speaker[i];
49
1.68k
                    replaced = true;
50
1.68k
                    break;
51
1.68k
                }
52
397k
            }
53
199k
        }
54
10.6k
        if (replaced && mp_chmap_is_valid(&t)) {
55
540
            *map = t;
56
540
            return true;
57
540
        }
58
10.6k
    }
59
4.89k
    return false;
60
5.43k
}
61
62
// These go strictly from the first to the second entry and always use the
63
// full layout (possibly reordered and/or padding channels added).
64
static const struct mp_chmap preferred_remix[][2] = {
65
    // mono can be perfectly played as stereo
66
    { MP_CHMAP_INIT_MONO, MP_CHMAP_INIT_STEREO },
67
};
68
69
// Conversion from src to dst is explicitly encouraged and should be preferred
70
// over "mathematical" upmixes or downmixes (which minimize lost channels).
71
static bool test_preferred_remix(const struct mp_chmap *src,
72
                                 const struct mp_chmap *dst)
73
0
{
74
0
    struct mp_chmap src_p = *src, dst_p = *dst;
75
0
    mp_chmap_remove_na(&src_p);
76
0
    mp_chmap_remove_na(&dst_p);
77
0
    for (int n = 0; n < MP_ARRAY_SIZE(preferred_remix); n++) {
78
0
        if (mp_chmap_equals_reordered(&src_p, &preferred_remix[n][0]) &&
79
0
            mp_chmap_equals_reordered(&dst_p, &preferred_remix[n][1]))
80
0
            return true;
81
0
    }
82
0
    return false;
83
0
}
84
85
// Allow all channel layouts that can be expressed with mp_chmap.
86
// (By default, all layouts are rejected.)
87
void mp_chmap_sel_add_any(struct mp_chmap_sel *s)
88
27.5k
{
89
27.5k
    s->allow_any = true;
90
27.5k
}
91
92
// Allow all waveext formats, and force waveext channel order.
93
void mp_chmap_sel_add_waveext(struct mp_chmap_sel *s)
94
1
{
95
1
    s->allow_waveext = true;
96
1
}
97
98
// Add a channel map that should be allowed.
99
void mp_chmap_sel_add_map(struct mp_chmap_sel *s, const struct mp_chmap *map)
100
34.4k
{
101
34.4k
    if (!mp_chmap_is_valid(map))
102
0
        return;
103
34.4k
    if (!s->chmaps)
104
34.4k
        s->chmaps = s->chmaps_storage;
105
34.4k
    if (s->num_chmaps == MP_ARRAY_SIZE(s->chmaps_storage)) {
106
0
        if (!s->tmp)
107
0
            return;
108
0
        s->chmaps = talloc_memdup(s->tmp, s->chmaps, sizeof(s->chmaps_storage));
109
0
    }
110
34.4k
    if (s->chmaps != s->chmaps_storage)
111
0
        MP_TARRAY_GROW(s->tmp, s->chmaps, s->num_chmaps);
112
34.4k
    s->chmaps[s->num_chmaps++] = *map;
113
34.4k
}
114
115
// Allow all waveext formats in default order.
116
void mp_chmap_sel_add_waveext_def(struct mp_chmap_sel *s)
117
0
{
118
0
    for (int n = 1; n <= MP_NUM_CHANNELS; n++) {
119
0
        struct mp_chmap map;
120
0
        mp_chmap_from_channels(&map, n);
121
0
        mp_chmap_sel_add_map(s, &map);
122
0
    }
123
0
}
124
125
// Whitelist a speaker (MP_SPEAKER_ID_...). All layouts that contain whitelisted
126
// speakers are allowed.
127
void mp_chmap_sel_add_speaker(struct mp_chmap_sel *s, int id)
128
0
{
129
0
    mp_assert(id >= 0 && id < MP_SPEAKER_ID_COUNT);
130
0
    s->speakers[id] = true;
131
0
}
132
133
static bool test_speakers(const struct mp_chmap_sel *s, struct mp_chmap *map)
134
42.3k
{
135
42.3k
    for (int n = 0; n < map->num; n++) {
136
42.3k
        if (!s->speakers[map->speaker[n]])
137
42.3k
            return false;
138
42.3k
    }
139
0
    return true;
140
42.3k
}
141
142
static bool test_maps(const struct mp_chmap_sel *s, struct mp_chmap *map)
143
42.3k
{
144
56.7k
    for (int n = 0; n < s->num_chmaps; n++) {
145
42.3k
        if (mp_chmap_equals_reordered(&s->chmaps[n], map)) {
146
28.0k
            *map = s->chmaps[n];
147
28.0k
            return true;
148
28.0k
        }
149
42.3k
    }
150
14.3k
    return false;
151
42.3k
}
152
153
static bool test_waveext(const struct mp_chmap_sel *s, struct mp_chmap *map)
154
42.3k
{
155
42.3k
    if (s->allow_waveext) {
156
1
        struct mp_chmap t = *map;
157
1
        mp_chmap_reorder_to_waveext(&t);
158
1
        if (mp_chmap_is_waveext(&t)) {
159
1
            *map = t;
160
1
            return true;
161
1
        }
162
1
    }
163
42.3k
    return false;
164
42.3k
}
165
166
static bool test_layout(const struct mp_chmap_sel *s, struct mp_chmap *map)
167
69.9k
{
168
69.9k
    if (!mp_chmap_is_valid(map))
169
0
        return false;
170
171
69.9k
    return s->allow_any || test_waveext(s, map) || test_speakers(s, map) ||
172
42.3k
           test_maps(s, map);
173
69.9k
}
174
175
// Determine which channel map to use given a source channel map, and various
176
// parameters restricting possible choices. If the map doesn't match, select
177
// a fallback and set it.
178
// If no matching layout is found, a reordered layout may be returned.
179
// If that is not possible, a fallback for up/downmixing may be returned.
180
// If no choice is possible, set *map to empty.
181
bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map)
182
61.7k
{
183
61.7k
    if (test_layout(s, map))
184
55.5k
        return true;
185
6.24k
    if (mp_chmap_is_unknown(map)) {
186
2.12k
        struct mp_chmap t = {0};
187
2.12k
        if (mp_chmap_sel_get_def(s, &t, map->num) && test_layout(s, &t)) {
188
25
            *map = t;
189
25
            return true;
190
25
        }
191
2.12k
    }
192
193
6.22k
    if (mp_chmap_sel_fallback(s, map))
194
3.50k
        return true;
195
196
8.15k
    for (int i = 0; i < MP_ARRAY_SIZE(speaker_replacements); i++) {
197
5.43k
        struct mp_chmap  t = *map;
198
5.43k
        struct mp_chmap *r = (struct mp_chmap *)speaker_replacements[i];
199
5.43k
        if (replace_speakers(&t, r) && test_layout(s, &t)) {
200
0
            *map = t;
201
0
            return true;
202
0
        }
203
5.43k
    }
204
205
    // Fallback to mono/stereo as last resort
206
2.71k
    *map = (struct mp_chmap) MP_CHMAP_INIT_STEREO;
207
2.71k
    if (test_layout(s, map))
208
0
        return true;
209
2.71k
    *map = (struct mp_chmap) MP_CHMAP_INIT_MONO;
210
2.71k
    if (test_layout(s, map))
211
0
        return true;
212
2.71k
    *map = (struct mp_chmap) {0};
213
2.71k
    return false;
214
2.71k
}
215
216
// Like mp_chmap_diffn(), but find the minimum difference with all possible
217
// speaker replacements considered.
218
static int mp_chmap_diffn_r(const struct mp_chmap *a, const struct mp_chmap *b)
219
0
{
220
0
    int mindiff = INT_MAX;
221
222
0
    for (int i = -1; i < (int)MP_ARRAY_SIZE(speaker_replacements); i++) {
223
0
        struct mp_chmap ar = *a;
224
0
        if (i >= 0) {
225
0
            struct mp_chmap *r = (struct mp_chmap *)speaker_replacements[i];
226
0
            if (!replace_speakers(&ar, r))
227
0
                continue;
228
0
        }
229
0
        int d = mp_chmap_diffn(&ar, b);
230
0
        if (d < mindiff)
231
0
            mindiff = d;
232
0
    }
233
234
    // Special-case: we consider stereo a replacement for mono. (This is not
235
    // true in the other direction. Also, fl-fr is generally not a replacement
236
    // for fc. Thus it's not part of the speaker replacement list.)
237
0
    struct mp_chmap mono   = MP_CHMAP_INIT_MONO;
238
0
    struct mp_chmap stereo = MP_CHMAP_INIT_STEREO;
239
0
    if (mp_chmap_equals(&mono, b) && mp_chmap_equals(&stereo, a))
240
0
        mindiff = 0;
241
242
0
    return mindiff;
243
0
}
244
245
// Decide whether we should prefer old or new for the requested layout.
246
// Return true if new should be used, false if old should be used.
247
// If old is empty, always return new (initial case).
248
static bool mp_chmap_is_better(struct mp_chmap *req, struct mp_chmap *old,
249
                               struct mp_chmap *new)
250
3.50k
{
251
    // Initial case
252
3.50k
    if (!old->num)
253
3.50k
        return true;
254
255
    // Exact pick - this also ensures that the best layout is chosen if the
256
    // layouts are the same, but with different order of channels.
257
0
    if (mp_chmap_equals(req, old))
258
0
        return false;
259
0
    if (mp_chmap_equals(req, new))
260
0
        return true;
261
262
    // If there's no exact match, strictly do a preferred conversion.
263
0
    bool old_pref = test_preferred_remix(req, old);
264
0
    bool new_pref = test_preferred_remix(req, new);
265
0
    if (old_pref && !new_pref)
266
0
        return false;
267
0
    if (!old_pref && new_pref)
268
0
        return true;
269
270
0
    int old_lost_r = mp_chmap_diffn_r(req, old); // num. channels only in req
271
0
    int new_lost_r = mp_chmap_diffn_r(req, new);
272
273
    // Imperfect upmix (no real superset) - minimize lost channels
274
0
    if (new_lost_r != old_lost_r)
275
0
        return new_lost_r < old_lost_r;
276
277
0
    struct mp_chmap old_p = *old, new_p = *new;
278
0
    mp_chmap_remove_na(&old_p);
279
0
    mp_chmap_remove_na(&new_p);
280
281
    // If the situation is equal with replaced speakers, but the replacement is
282
    // perfect for only one of them, let the better one win. This prefers
283
    // inexact equivalents over exact supersets.
284
0
    bool perfect_r_new = !new_lost_r && new_p.num <= old_p.num;
285
0
    bool perfect_r_old = !old_lost_r && old_p.num <= new_p.num;
286
0
    if (perfect_r_new != perfect_r_old)
287
0
        return perfect_r_new;
288
289
0
    int old_lost = mp_chmap_diffn(req, old);
290
0
    int new_lost = mp_chmap_diffn(req, new);
291
    // If the situation is equal with replaced speakers, pick the better one,
292
    // even if it means an upmix.
293
0
    if (new_lost != old_lost)
294
0
        return new_lost < old_lost;
295
296
    // Some kind of upmix. If it's perfect, prefer the smaller one. Even if not,
297
    // both have equal loss, so also prefer the smaller one.
298
    // Drop padding channels (NA) for the sake of this check, as the number of
299
    // padding channels isn't really meaningful.
300
0
    if (new_p.num != old_p.num)
301
0
        return new_p.num < old_p.num;
302
303
    // Again, with physical channels (minimizes number of NA channels).
304
0
    return new->num < old->num;
305
0
}
306
307
// Determine which channel map to fallback to given a source channel map.
308
bool mp_chmap_sel_fallback(const struct mp_chmap_sel *s, struct mp_chmap *map)
309
33.7k
{
310
33.7k
    struct mp_chmap best = {0};
311
312
40.0k
    for (int n = 0; n < s->num_chmaps; n++) {
313
6.22k
        struct mp_chmap e = s->chmaps[n];
314
315
6.22k
        if (mp_chmap_is_unknown(&e))
316
2.71k
            continue;
317
318
3.50k
        if (mp_chmap_is_better(map, &best, &e))
319
3.50k
            best = e;
320
3.50k
    }
321
322
33.7k
    if (best.num) {
323
3.50k
        *map = best;
324
3.50k
        return true;
325
3.50k
    }
326
327
30.2k
    return false;
328
33.7k
}
329
330
// Set map to a default layout with num channels. Used for audio APIs that
331
// return a channel count as part of format negotiation, but give no
332
// information about the channel layout.
333
// If the channel count is correct, do nothing and leave *map untouched.
334
bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map,
335
                          int num)
336
2.12k
{
337
2.12k
    if (map->num != num) {
338
2.12k
        *map = (struct mp_chmap) {0};
339
        // Set of speakers or waveext might allow it.
340
2.12k
        struct mp_chmap t;
341
2.12k
        mp_chmap_from_channels(&t, num);
342
2.12k
        mp_chmap_reorder_to_waveext(&t);
343
2.12k
        if (test_layout(s, &t)) {
344
3
            *map = t;
345
2.12k
        } else {
346
4.22k
            for (int n = 0; n < s->num_chmaps; n++) {
347
2.12k
                if (s->chmaps[n].num == num) {
348
22
                    *map = s->chmaps[n];
349
22
                    break;
350
22
                }
351
2.12k
            }
352
2.12k
        }
353
2.12k
    }
354
2.12k
    return map->num > 0;
355
2.12k
}
356
357
// Print the set of allowed channel layouts.
358
void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev)
359
27.5k
{
360
27.5k
    if (!mp_msg_test(log, lev))
361
0
        return;
362
363
27.5k
    for (int i = 0; i < s->num_chmaps; i++)
364
0
        mp_msg(log, lev, " - %s\n", mp_chmap_to_str(&s->chmaps[i]));
365
1.81M
    for (int i = 0; i < MP_SPEAKER_ID_COUNT; i++) {
366
1.79M
        if (!s->speakers[i])
367
1.79M
            continue;
368
0
        struct mp_chmap l = {.num = 1, .speaker = { i }};
369
0
        mp_msg(log, lev, " - #%s\n",
370
0
                    i == MP_SPEAKER_ID_FC ? "fc" : mp_chmap_to_str_hr(&l));
371
0
    }
372
27.5k
    if (s->allow_waveext)
373
1
        mp_msg(log, lev, " - waveext\n");
374
27.5k
    if (s->allow_any)
375
27.5k
        mp_msg(log, lev, " - anything\n");
376
27.5k
}
377
378
// Select a channel map from the given list that fits best to c. Don't change
379
// *c if there's no match, or the list is empty.
380
void mp_chmap_sel_list(struct mp_chmap *c, struct mp_chmap *maps, int num_maps)
381
27.5k
{
382
    // This is a separate function to keep messing with mp_chmap_sel internals
383
    // within this source file.
384
27.5k
    struct mp_chmap_sel sel = {
385
27.5k
        .chmaps = maps,
386
27.5k
        .num_chmaps = num_maps,
387
27.5k
    };
388
27.5k
    mp_chmap_sel_fallback(&sel, c);
389
27.5k
}