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 | | |
21 | | #include <libavutil/common.h> |
22 | | |
23 | | #include "common/common.h" |
24 | | #include "common/msg.h" |
25 | | #include "chmap.h" |
26 | | |
27 | | // Names taken from libavutil/channel_layout.c (Not accessible by API.) |
28 | | // Use of these names is hard-coded in some places (e.g. ao_alsa.c) |
29 | | static const char *const speaker_names[MP_SPEAKER_ID_COUNT][2] = { |
30 | | [MP_SPEAKER_ID_FL] = {"fl", "front left"}, |
31 | | [MP_SPEAKER_ID_FR] = {"fr", "front right"}, |
32 | | [MP_SPEAKER_ID_FC] = {"fc", "front center"}, |
33 | | [MP_SPEAKER_ID_LFE] = {"lfe", "low frequency"}, |
34 | | [MP_SPEAKER_ID_BL] = {"bl", "back left"}, |
35 | | [MP_SPEAKER_ID_BR] = {"br", "back right"}, |
36 | | [MP_SPEAKER_ID_FLC] = {"flc", "front left-of-center"}, |
37 | | [MP_SPEAKER_ID_FRC] = {"frc", "front right-of-center"}, |
38 | | [MP_SPEAKER_ID_BC] = {"bc", "back center"}, |
39 | | [MP_SPEAKER_ID_SL] = {"sl", "side left"}, |
40 | | [MP_SPEAKER_ID_SR] = {"sr", "side right"}, |
41 | | [MP_SPEAKER_ID_TC] = {"tc", "top center"}, |
42 | | [MP_SPEAKER_ID_TFL] = {"tfl", "top front left"}, |
43 | | [MP_SPEAKER_ID_TFC] = {"tfc", "top front center"}, |
44 | | [MP_SPEAKER_ID_TFR] = {"tfr", "top front right"}, |
45 | | [MP_SPEAKER_ID_TBL] = {"tbl", "top back left"}, |
46 | | [MP_SPEAKER_ID_TBC] = {"tbc", "top back center"}, |
47 | | [MP_SPEAKER_ID_TBR] = {"tbr", "top back right"}, |
48 | | [MP_SPEAKER_ID_DL] = {"dl", "downmix left"}, |
49 | | [MP_SPEAKER_ID_DR] = {"dr", "downmix right"}, |
50 | | [MP_SPEAKER_ID_WL] = {"wl", "wide left"}, |
51 | | [MP_SPEAKER_ID_WR] = {"wr", "wide right"}, |
52 | | [MP_SPEAKER_ID_SDL] = {"sdl", "surround direct left"}, |
53 | | [MP_SPEAKER_ID_SDR] = {"sdr", "surround direct right"}, |
54 | | [MP_SPEAKER_ID_LFE2] = {"lfe2", "low frequency 2"}, |
55 | | [MP_SPEAKER_ID_TSL] = {"tsl", "top side left"}, |
56 | | [MP_SPEAKER_ID_TSR] = {"tsr", "top side right"}, |
57 | | [MP_SPEAKER_ID_BFC] = {"bfc", "bottom front center"}, |
58 | | [MP_SPEAKER_ID_BFL] = {"bfl", "bottom front left"}, |
59 | | [MP_SPEAKER_ID_BFR] = {"bfr", "bottom front right"}, |
60 | | [MP_SPEAKER_ID_NA] = {"na", "not available"}, |
61 | | }; |
62 | | |
63 | | // Names taken from libavutil/channel_layout.c (Not accessible by API.) |
64 | | // Channel order corresponds to lavc/waveex, except for the alsa entries. |
65 | | static const char *const std_layout_names[][2] = { |
66 | | {"empty", ""}, // not in lavc |
67 | | {"mono", "fc"}, |
68 | | {"1.0", "fc"}, // not in lavc |
69 | | {"stereo", "fl-fr"}, |
70 | | {"2.0", "fl-fr"}, // not in lavc |
71 | | {"2.1", "fl-fr-lfe"}, |
72 | | {"3.0", "fl-fr-fc"}, |
73 | | {"3.0(back)", "fl-fr-bc"}, |
74 | | {"4.0", "fl-fr-fc-bc"}, |
75 | | {"quad", "fl-fr-bl-br"}, |
76 | | {"quad(side)", "fl-fr-sl-sr"}, |
77 | | {"3.1", "fl-fr-fc-lfe"}, |
78 | | {"3.1(back)", "fl-fr-lfe-bc"}, // not in lavc |
79 | | {"5.0", "fl-fr-fc-bl-br"}, |
80 | | {"5.0(alsa)", "fl-fr-bl-br-fc"}, // not in lavc |
81 | | {"5.0(side)", "fl-fr-fc-sl-sr"}, |
82 | | {"4.1", "fl-fr-fc-lfe-bc"}, |
83 | | {"4.1(alsa)", "fl-fr-bl-br-lfe"}, // not in lavc |
84 | | {"5.1", "fl-fr-fc-lfe-bl-br"}, |
85 | | {"5.1(alsa)", "fl-fr-bl-br-fc-lfe"}, // not in lavc |
86 | | {"5.1(side)", "fl-fr-fc-lfe-sl-sr"}, |
87 | | {"6.0", "fl-fr-fc-bc-sl-sr"}, |
88 | | {"6.0(front)", "fl-fr-flc-frc-sl-sr"}, |
89 | | {"hexagonal", "fl-fr-fc-bl-br-bc"}, |
90 | | {"6.1", "fl-fr-fc-lfe-bc-sl-sr"}, |
91 | | {"6.1(back)", "fl-fr-fc-lfe-bl-br-bc"}, |
92 | | {"6.1(top)", "fl-fr-fc-lfe-bl-br-tc"}, // not in lavc |
93 | | {"6.1(front)", "fl-fr-lfe-flc-frc-sl-sr"}, |
94 | | {"7.0", "fl-fr-fc-bl-br-sl-sr"}, |
95 | | {"7.0(front)", "fl-fr-fc-flc-frc-sl-sr"}, |
96 | | {"7.0(rear)", "fl-fr-fc-bl-br-sdl-sdr"}, // not in lavc |
97 | | {"7.1", "fl-fr-fc-lfe-bl-br-sl-sr"}, |
98 | | {"7.1(alsa)", "fl-fr-bl-br-fc-lfe-sl-sr"}, // not in lavc |
99 | | {"7.1(wide)", "fl-fr-fc-lfe-bl-br-flc-frc"}, |
100 | | {"7.1(wide-side)", "fl-fr-fc-lfe-flc-frc-sl-sr"}, |
101 | | {"7.1(top)", "fl-fr-fc-lfe-bl-br-tfl-tfr"}, |
102 | | {"7.1(rear)", "fl-fr-fc-lfe-bl-br-sdl-sdr"}, // not in lavc |
103 | | {"octagonal", "fl-fr-fc-bl-br-bc-sl-sr"}, |
104 | | {"cube", "fl-fr-bl-br-tfl-tfr-tbl-tbr"}, |
105 | | {"hexadecagonal", "fl-fr-fc-bl-br-bc-sl-sr-tfc-tfl-tfr-tbl-tbc-tbr-wl-wr"}, |
106 | | {"downmix", "fl-fr"}, |
107 | | {"22.2", "fl-fr-fc-lfe-bl-br-flc-frc-bc-sl-sr-tc-tfl-tfc-tfr-tbl-tbc-tbr-lfe2-tsl-tsr-bfc-bfl-bfr"}, |
108 | | {"auto", ""}, // not in lavc |
109 | | {0} |
110 | | }; |
111 | | |
112 | | static const struct mp_chmap default_layouts[] = { |
113 | | {0}, // empty |
114 | | MP_CHMAP_INIT_MONO, // mono |
115 | | MP_CHMAP2(FL, FR), // stereo |
116 | | MP_CHMAP3(FL, FR, LFE), // 2.1 |
117 | | MP_CHMAP4(FL, FR, FC, BC), // 4.0 |
118 | | MP_CHMAP5(FL, FR, FC, BL, BR), // 5.0 |
119 | | MP_CHMAP6(FL, FR, FC, LFE, BL, BR), // 5.1 |
120 | | MP_CHMAP7(FL, FR, FC, LFE, BC, SL, SR), // 6.1 |
121 | | MP_CHMAP8(FL, FR, FC, LFE, BL, BR, SL, SR), // 7.1 |
122 | | }; |
123 | | |
124 | | // Returns true if speakers are mapped uniquely, and there's at least 1 channel. |
125 | | bool mp_chmap_is_valid(const struct mp_chmap *src) |
126 | 37.4M | { |
127 | 37.4M | bool mapped[MP_SPEAKER_ID_COUNT] = {0}; |
128 | 88.5M | for (int n = 0; n < src->num; n++) { |
129 | 51.0M | int sp = src->speaker[n]; |
130 | 51.0M | if (sp >= MP_SPEAKER_ID_COUNT || mapped[sp]) |
131 | 52 | return false; |
132 | 51.0M | if (sp != MP_SPEAKER_ID_NA) |
133 | 46.7M | mapped[sp] = true; |
134 | 51.0M | } |
135 | 37.4M | return src->num > 0; |
136 | 37.4M | } |
137 | | |
138 | | bool mp_chmap_is_empty(const struct mp_chmap *src) |
139 | 0 | { |
140 | 0 | return src->num == 0; |
141 | 0 | } |
142 | | |
143 | | // Return true if the channel map defines the number of the channels only, and |
144 | | // the channels have to meaning associated with them. |
145 | | bool mp_chmap_is_unknown(const struct mp_chmap *src) |
146 | 2.38M | { |
147 | 3.02M | for (int n = 0; n < src->num; n++) { |
148 | 2.95M | if (src->speaker[n] != MP_SPEAKER_ID_NA) |
149 | 2.30M | return false; |
150 | 2.95M | } |
151 | 74.2k | return mp_chmap_is_valid(src); |
152 | 2.38M | } |
153 | | |
154 | | // Note: empty channel maps compare as equal. Invalid ones can equal too. |
155 | | bool mp_chmap_equals(const struct mp_chmap *a, const struct mp_chmap *b) |
156 | 21.1M | { |
157 | 21.1M | if (a->num != b->num) |
158 | 734k | return false; |
159 | 46.2M | for (int n = 0; n < a->num; n++) { |
160 | 26.2M | if (a->speaker[n] != b->speaker[n]) |
161 | 430k | return false; |
162 | 26.2M | } |
163 | 19.9M | return true; |
164 | 20.3M | } |
165 | | |
166 | | // Whether they use the same speakers (even if in different order). |
167 | | bool mp_chmap_equals_reordered(const struct mp_chmap *a, const struct mp_chmap *b) |
168 | 803k | { |
169 | 803k | struct mp_chmap t1 = *a, t2 = *b; |
170 | 803k | mp_chmap_reorder_norm(&t1); |
171 | 803k | mp_chmap_reorder_norm(&t2); |
172 | 803k | return mp_chmap_equals(&t1, &t2); |
173 | 803k | } |
174 | | |
175 | | bool mp_chmap_is_stereo(const struct mp_chmap *src) |
176 | 0 | { |
177 | 0 | static const struct mp_chmap stereo = MP_CHMAP_INIT_STEREO; |
178 | 0 | return mp_chmap_equals(src, &stereo); |
179 | 0 | } |
180 | | |
181 | | static int comp_uint8(const void *a, const void *b) |
182 | 4.39M | { |
183 | 4.39M | return *(const uint8_t *)a - *(const uint8_t *)b; |
184 | 4.39M | } |
185 | | |
186 | | // Reorder channels to normal order, with monotonically increasing speaker IDs. |
187 | | // We define this order as the same order used with waveex. |
188 | | void mp_chmap_reorder_norm(struct mp_chmap *map) |
189 | 1.60M | { |
190 | 1.60M | uint8_t *arr = &map->speaker[0]; |
191 | 1.60M | qsort(arr, map->num, 1, comp_uint8); |
192 | 1.60M | } |
193 | | |
194 | | // Remove silent (NA) channels, if any. |
195 | | void mp_chmap_remove_na(struct mp_chmap *map) |
196 | 182k | { |
197 | 182k | struct mp_chmap new = {0}; |
198 | 706k | for (int n = 0; n < map->num; n++) { |
199 | 523k | int sp = map->speaker[n]; |
200 | 523k | if (sp != MP_SPEAKER_ID_NA) |
201 | 304k | new.speaker[new.num++] = map->speaker[n]; |
202 | 523k | } |
203 | 182k | *map = new; |
204 | 182k | } |
205 | | |
206 | | // Add silent (NA) channels to map until map->num >= num. |
207 | | void mp_chmap_fill_na(struct mp_chmap *map, int num) |
208 | 5.47k | { |
209 | 5.47k | mp_assert(num <= MP_NUM_CHANNELS); |
210 | 5.47k | while (map->num < num) |
211 | 0 | map->speaker[map->num++] = MP_SPEAKER_ID_NA; |
212 | 5.47k | } |
213 | | |
214 | | // Set *dst to a standard layout with the given number of channels. |
215 | | // If the number of channels is invalid, an invalid map is set, and |
216 | | // mp_chmap_is_valid(dst) will return false. |
217 | | void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels) |
218 | 130k | { |
219 | 130k | *dst = (struct mp_chmap) {0}; |
220 | 130k | if (num_channels >= 0 && num_channels < MP_ARRAY_SIZE(default_layouts)) |
221 | 91.2k | *dst = default_layouts[num_channels]; |
222 | 130k | if (!dst->num) |
223 | 52.7k | mp_chmap_set_unknown(dst, num_channels); |
224 | 130k | } |
225 | | |
226 | | // Set *dst to an unknown layout for the given numbers of channels. |
227 | | // If the number of channels is invalid, an invalid map is set, and |
228 | | // mp_chmap_is_valid(dst) will return false. |
229 | | // A mp_chmap with all entries set to NA is treated specially in some |
230 | | // contexts (watch out for mp_chmap_is_unknown()). |
231 | | void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels) |
232 | 78.8k | { |
233 | 78.8k | if (num_channels < 0 || num_channels > MP_NUM_CHANNELS) { |
234 | 24.4k | *dst = (struct mp_chmap) {0}; |
235 | 54.4k | } else { |
236 | 54.4k | dst->num = num_channels; |
237 | 542k | for (int n = 0; n < dst->num; n++) |
238 | 487k | dst->speaker[n] = MP_SPEAKER_ID_NA; |
239 | 54.4k | } |
240 | 78.8k | } |
241 | | |
242 | | // Return the ffmpeg channel layout as in <libavutil/channel_layout.h>. |
243 | | // Speakers not representable by ffmpeg are dropped. |
244 | | // Warning: this ignores the order of the channels, and will return a channel |
245 | | // mask even if the order is different from libavcodec's. |
246 | | // Also, "unknown" channel maps are translated to non-sense channel |
247 | | // maps with the same number of channels. |
248 | | uint64_t mp_chmap_to_lavc_unchecked(const struct mp_chmap *src) |
249 | 624k | { |
250 | 624k | struct mp_chmap t = *src; |
251 | 624k | if (t.num > 64) |
252 | 0 | return 0; |
253 | | // lavc has no concept for unknown layouts yet, so pick something that does |
254 | | // the job of signaling the number of channels, even if it makes no sense |
255 | | // as a proper layout. |
256 | 624k | if (mp_chmap_is_unknown(&t)) |
257 | 11.2k | return t.num == 64 ? (uint64_t)-1 : (1ULL << t.num) - 1; |
258 | 613k | uint64_t mask = 0; |
259 | 2.06M | for (int n = 0; n < t.num; n++) { |
260 | 1.44M | if (t.speaker[n] < 64) // ignore MP_SPEAKER_ID_NA etc. |
261 | 1.44M | mask |= 1ULL << t.speaker[n]; |
262 | 1.44M | } |
263 | 613k | return mask; |
264 | 624k | } |
265 | | |
266 | | // Return the ffmpeg channel layout as in <libavutil/channel_layout.h>. |
267 | | // Returns 0 if the channel order doesn't match lavc's or if it's invalid. |
268 | | uint64_t mp_chmap_to_lavc(const struct mp_chmap *src) |
269 | 627k | { |
270 | 627k | if (!mp_chmap_is_lavc(src)) |
271 | 20.0k | return 0; |
272 | 607k | return mp_chmap_to_lavc_unchecked(src); |
273 | 627k | } |
274 | | |
275 | | // Set channel map from the ffmpeg channel layout as in |
276 | | // <libavutil/channel_layout.h>. |
277 | | // If the number of channels exceed MP_NUM_CHANNELS, set dst to empty. |
278 | | void mp_chmap_from_lavc(struct mp_chmap *dst, uint64_t src) |
279 | 6.59M | { |
280 | 6.59M | dst->num = 0; |
281 | 428M | for (int n = 0; n < 64; n++) { |
282 | 421M | if (src & (1ULL << n)) { |
283 | 7.31M | if (dst->num >= MP_NUM_CHANNELS) { |
284 | 0 | dst->num = 0; |
285 | 0 | return; |
286 | 0 | } |
287 | 7.31M | dst->speaker[dst->num] = n; |
288 | 7.31M | dst->num++; |
289 | 7.31M | } |
290 | 421M | } |
291 | 6.59M | } |
292 | | |
293 | | bool mp_chmap_is_lavc(const struct mp_chmap *src) |
294 | 627k | { |
295 | 627k | if (!mp_chmap_is_valid(src)) |
296 | 20.0k | return false; |
297 | 607k | if (mp_chmap_is_unknown(src)) |
298 | 0 | return true; |
299 | | // lavc's channel layout is a bit mask, and channels are always ordered |
300 | | // from LSB to MSB speaker bits, so speaker IDs have to increase. |
301 | 607k | mp_assert(src->num > 0); |
302 | 1.41M | for (int n = 1; n < src->num; n++) { |
303 | 811k | if (src->speaker[n - 1] >= src->speaker[n]) |
304 | 0 | return false; |
305 | 811k | } |
306 | 2.02M | for (int n = 0; n < src->num; n++) { |
307 | 1.41M | if (src->speaker[n] >= 64) |
308 | 0 | return false; |
309 | 1.41M | } |
310 | 607k | return true; |
311 | 607k | } |
312 | | |
313 | | // Warning: for "unknown" channel maps, this returns something that may not |
314 | | // make sense. Invalid channel maps are not changed. |
315 | | void mp_chmap_reorder_to_lavc(struct mp_chmap *map) |
316 | 356 | { |
317 | 356 | if (!mp_chmap_is_valid(map)) |
318 | 0 | return; |
319 | 356 | uint64_t mask = mp_chmap_to_lavc_unchecked(map); |
320 | 356 | mp_chmap_from_lavc(map, mask); |
321 | 356 | } |
322 | | |
323 | | // Get reordering array for from->to reordering. from->to must have the same set |
324 | | // of speakers (i.e. same number and speaker IDs, just different order). Then, |
325 | | // for each speaker n, src[n] will be set such that: |
326 | | // to->speaker[n] = from->speaker[src[n]] |
327 | | // (src[n] gives the source channel for destination channel n) |
328 | | // If *from and *to don't contain the same set of speakers, then the above |
329 | | // invariant is not guaranteed. Instead, src[n] can be set to -1 if the channel |
330 | | // at to->speaker[n] is unmapped. |
331 | | void mp_chmap_get_reorder(int src[MP_NUM_CHANNELS], const struct mp_chmap *from, |
332 | | const struct mp_chmap *to) |
333 | 8.52k | { |
334 | 553k | for (int n = 0; n < MP_NUM_CHANNELS; n++) |
335 | 545k | src[n] = -1; |
336 | | |
337 | 8.52k | if (mp_chmap_is_unknown(from) || mp_chmap_is_unknown(to)) { |
338 | 42.0k | for (int n = 0; n < to->num; n++) |
339 | 36.5k | src[n] = n < from->num ? n : -1; |
340 | 5.47k | return; |
341 | 5.47k | } |
342 | | |
343 | 17.1k | for (int n = 0; n < to->num; n++) { |
344 | 48.8k | for (int i = 0; i < from->num; i++) { |
345 | 48.8k | if (to->speaker[n] == from->speaker[i]) { |
346 | 14.0k | src[n] = i; |
347 | 14.0k | break; |
348 | 14.0k | } |
349 | 48.8k | } |
350 | 14.0k | } |
351 | | |
352 | 17.1k | for (int n = 0; n < to->num; n++) |
353 | 14.0k | mp_assert(src[n] < 0 || (to->speaker[n] == from->speaker[src[n]])); |
354 | 3.04k | } |
355 | | |
356 | | // Return the number of channels only in a. |
357 | | int mp_chmap_diffn(const struct mp_chmap *a, const struct mp_chmap *b) |
358 | 0 | { |
359 | 0 | uint64_t a_mask = mp_chmap_to_lavc_unchecked(a); |
360 | 0 | uint64_t b_mask = mp_chmap_to_lavc_unchecked(b); |
361 | 0 | return av_popcount64((a_mask ^ b_mask) & a_mask); |
362 | 0 | } |
363 | | |
364 | | // Returns something like "fl-fr-fc". If there's a standard layout in lavc |
365 | | // order, return that, e.g. "3.0" instead of "fl-fr-fc". |
366 | | // Unassigned but valid speakers get names like "sp28". |
367 | | char *mp_chmap_to_str_buf(char *buf, size_t buf_size, const struct mp_chmap *src) |
368 | 460k | { |
369 | 460k | buf[0] = '\0'; |
370 | | |
371 | 460k | if (mp_chmap_is_unknown(src)) { |
372 | 9.36k | snprintf(buf, buf_size, "unknown%d", src->num); |
373 | 9.36k | return buf; |
374 | 9.36k | } |
375 | | |
376 | 1.28M | for (int n = 0; n < src->num; n++) { |
377 | 829k | int sp = src->speaker[n]; |
378 | 829k | const char *s = sp < MP_SPEAKER_ID_COUNT ? speaker_names[sp][0] : NULL; |
379 | 829k | char sp_buf[10]; |
380 | 829k | if (!s) { |
381 | 14.3k | snprintf(sp_buf, sizeof(sp_buf), "sp%d", sp); |
382 | 14.3k | s = sp_buf; |
383 | 14.3k | } |
384 | 829k | mp_snprintf_cat(buf, buf_size, "%s%s", n > 0 ? "-" : "", s); |
385 | 829k | } |
386 | | |
387 | | // To standard layout name |
388 | 2.08M | for (int n = 0; std_layout_names[n][0]; n++) { |
389 | 2.07M | if (strcmp(buf, std_layout_names[n][1]) == 0) { |
390 | 445k | snprintf(buf, buf_size, "%s", std_layout_names[n][0]); |
391 | 445k | break; |
392 | 445k | } |
393 | 2.07M | } |
394 | | |
395 | 451k | return buf; |
396 | 460k | } |
397 | | |
398 | | // If src can be parsed as channel map (as produced by mp_chmap_to_str()), |
399 | | // return true and set *dst. Otherwise, return false and don't change *dst. |
400 | | // Note: call mp_chmap_is_valid() to test whether the returned map is valid |
401 | | // the map could be empty, or contain multiply mapped channels |
402 | | bool mp_chmap_from_str(struct mp_chmap *dst, bstr src) |
403 | 822k | { |
404 | | // Single number corresponds to mp_chmap_from_channels() |
405 | 822k | if (src.len > 0) { |
406 | 822k | bstr t = src; |
407 | 822k | bool unknown = bstr_eatstart0(&t, "unknown"); |
408 | 822k | bstr rest; |
409 | 822k | long long count = bstrtoll(t, &rest, 10); |
410 | 822k | if (rest.len == 0) { |
411 | 15.1k | struct mp_chmap res; |
412 | 15.1k | if (unknown) { |
413 | 944 | mp_chmap_set_unknown(&res, count); |
414 | 14.2k | } else { |
415 | 14.2k | mp_chmap_from_channels(&res, count); |
416 | 14.2k | } |
417 | 15.1k | if (mp_chmap_is_valid(&res)) { |
418 | 15.0k | *dst = res; |
419 | 15.0k | return true; |
420 | 15.0k | } |
421 | 15.1k | } |
422 | 822k | } |
423 | | |
424 | | // From standard layout name |
425 | 7.03M | for (int n = 0; std_layout_names[n][0]; n++) { |
426 | 6.99M | if (bstr_equals0(src, std_layout_names[n][0])) { |
427 | 764k | src = bstr0(std_layout_names[n][1]); |
428 | 764k | break; |
429 | 764k | } |
430 | 6.99M | } |
431 | | |
432 | | // Explicit speaker list (separated by "-") |
433 | 807k | struct mp_chmap res = {0}; |
434 | 2.48M | while (src.len) { |
435 | 1.68M | bstr s; |
436 | 1.68M | bstr_split_tok(src, "-", &s, &src); |
437 | 1.68M | int speaker = -1; |
438 | 10.1M | for (int n = 0; n < MP_SPEAKER_ID_COUNT; n++) { |
439 | 10.1M | const char *name = speaker_names[n][0]; |
440 | 10.1M | if (name && bstr_equals0(s, name)) { |
441 | 1.63M | speaker = n; |
442 | 1.63M | break; |
443 | 1.63M | } |
444 | 10.1M | } |
445 | 1.68M | if (speaker < 0) { |
446 | 43.2k | if (bstr_eatstart0(&s, "sp")) { |
447 | 42.9k | long long sp = bstrtoll(s, &s, 0); |
448 | 42.9k | if (s.len == 0 && sp >= 0 && sp < MP_SPEAKER_ID_COUNT) |
449 | 42.8k | speaker = sp; |
450 | 42.9k | } |
451 | 43.2k | if (speaker < 0) |
452 | 400 | return false; |
453 | 43.2k | } |
454 | 1.68M | if (res.num >= MP_NUM_CHANNELS) |
455 | 1 | return false; |
456 | 1.68M | res.speaker[res.num] = speaker; |
457 | 1.68M | res.num++; |
458 | 1.68M | } |
459 | | |
460 | 807k | *dst = res; |
461 | 807k | return true; |
462 | 807k | } |
463 | | |
464 | | // Output a human readable "canonical" channel map string. Converting this from |
465 | | // a string back to a channel map can yield a different map, but the string |
466 | | // looks nicer. E.g. "fc-fl-fr-na" becomes "3.0". |
467 | | char *mp_chmap_to_str_hr_buf(char *buf, size_t buf_size, const struct mp_chmap *src) |
468 | 182k | { |
469 | 182k | struct mp_chmap map = *src; |
470 | 182k | mp_chmap_remove_na(&map); |
471 | 764k | for (int n = 0; std_layout_names[n][0]; n++) { |
472 | 763k | struct mp_chmap s; |
473 | 763k | if (mp_chmap_from_str(&s, bstr0(std_layout_names[n][0])) && |
474 | 763k | mp_chmap_equals_reordered(&s, &map)) |
475 | 181k | { |
476 | 181k | map = s; |
477 | 181k | break; |
478 | 181k | } |
479 | 763k | } |
480 | 182k | return mp_chmap_to_str_buf(buf, buf_size, &map); |
481 | 182k | } |
482 | | |
483 | | mp_ch_layout_tuple *mp_iterate_builtin_layouts(void **opaque) |
484 | 0 | { |
485 | 0 | uintptr_t i = (uintptr_t)*opaque; |
486 | |
|
487 | 0 | if (i >= MP_ARRAY_SIZE(std_layout_names) || |
488 | 0 | !std_layout_names[i][0]) |
489 | 0 | return NULL; |
490 | | |
491 | 0 | *opaque = (void *)(i + 1); |
492 | |
|
493 | 0 | if (std_layout_names[i][1][0] == '\0') { |
494 | 0 | return mp_iterate_builtin_layouts(opaque); |
495 | 0 | } |
496 | | |
497 | 0 | return &std_layout_names[i]; |
498 | 0 | } |
499 | | |
500 | | void mp_chmap_print_help(struct mp_log *log) |
501 | 1 | { |
502 | 1 | mp_info(log, "Speakers:\n"); |
503 | 66 | for (int n = 0; n < MP_SPEAKER_ID_COUNT; n++) { |
504 | 65 | if (speaker_names[n][0]) |
505 | 31 | mp_info(log, " %-16s (%s)\n", |
506 | 65 | speaker_names[n][0], speaker_names[n][1]); |
507 | 65 | } |
508 | 1 | mp_info(log, "Standard layouts:\n"); |
509 | 44 | for (int n = 0; std_layout_names[n][0]; n++) { |
510 | 43 | mp_info(log, " %-16s (%s)\n", |
511 | 43 | std_layout_names[n][0], std_layout_names[n][1]); |
512 | 43 | } |
513 | 65 | for (int n = 0; n < MP_NUM_CHANNELS; n++) |
514 | 64 | mp_info(log, " unknown%d\n", n + 1); |
515 | 1 | } |