Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * Copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at> | ||
3 | * | ||
4 | * This file is part of FFmpeg. | ||
5 | * | ||
6 | * FFmpeg is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU Lesser General Public | ||
8 | * License as published by the Free Software Foundation; either | ||
9 | * version 2.1 of the License, or (at your option) any later version. | ||
10 | * | ||
11 | * FFmpeg is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | * Lesser General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU Lesser General Public | ||
17 | * License along with FFmpeg; if not, write to the Free Software | ||
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file | ||
23 | * audio channel layout utility functions | ||
24 | */ | ||
25 | |||
26 | #include <stdint.h> | ||
27 | #include <stdlib.h> | ||
28 | #include <string.h> | ||
29 | |||
30 | #include "avassert.h" | ||
31 | #include "channel_layout.h" | ||
32 | #include "bprint.h" | ||
33 | #include "common.h" | ||
34 | #include "error.h" | ||
35 | #include "macros.h" | ||
36 | #include "mem.h" | ||
37 | #include "opt.h" | ||
38 | |||
39 | #define CHAN_IS_AMBI(x) ((x) >= AV_CHAN_AMBISONIC_BASE &&\ | ||
40 | (x) <= AV_CHAN_AMBISONIC_END) | ||
41 | |||
42 | struct channel_name { | ||
43 | const char *name; | ||
44 | const char *description; | ||
45 | }; | ||
46 | |||
47 | static const struct channel_name channel_names[] = { | ||
48 | [AV_CHAN_FRONT_LEFT ] = { "FL", "front left" }, | ||
49 | [AV_CHAN_FRONT_RIGHT ] = { "FR", "front right" }, | ||
50 | [AV_CHAN_FRONT_CENTER ] = { "FC", "front center" }, | ||
51 | [AV_CHAN_LOW_FREQUENCY ] = { "LFE", "low frequency" }, | ||
52 | [AV_CHAN_BACK_LEFT ] = { "BL", "back left" }, | ||
53 | [AV_CHAN_BACK_RIGHT ] = { "BR", "back right" }, | ||
54 | [AV_CHAN_FRONT_LEFT_OF_CENTER ] = { "FLC", "front left-of-center" }, | ||
55 | [AV_CHAN_FRONT_RIGHT_OF_CENTER] = { "FRC", "front right-of-center" }, | ||
56 | [AV_CHAN_BACK_CENTER ] = { "BC", "back center" }, | ||
57 | [AV_CHAN_SIDE_LEFT ] = { "SL", "side left" }, | ||
58 | [AV_CHAN_SIDE_RIGHT ] = { "SR", "side right" }, | ||
59 | [AV_CHAN_TOP_CENTER ] = { "TC", "top center" }, | ||
60 | [AV_CHAN_TOP_FRONT_LEFT ] = { "TFL", "top front left" }, | ||
61 | [AV_CHAN_TOP_FRONT_CENTER ] = { "TFC", "top front center" }, | ||
62 | [AV_CHAN_TOP_FRONT_RIGHT ] = { "TFR", "top front right" }, | ||
63 | [AV_CHAN_TOP_BACK_LEFT ] = { "TBL", "top back left" }, | ||
64 | [AV_CHAN_TOP_BACK_CENTER ] = { "TBC", "top back center" }, | ||
65 | [AV_CHAN_TOP_BACK_RIGHT ] = { "TBR", "top back right" }, | ||
66 | [AV_CHAN_STEREO_LEFT ] = { "DL", "downmix left" }, | ||
67 | [AV_CHAN_STEREO_RIGHT ] = { "DR", "downmix right" }, | ||
68 | [AV_CHAN_WIDE_LEFT ] = { "WL", "wide left" }, | ||
69 | [AV_CHAN_WIDE_RIGHT ] = { "WR", "wide right" }, | ||
70 | [AV_CHAN_SURROUND_DIRECT_LEFT ] = { "SDL", "surround direct left" }, | ||
71 | [AV_CHAN_SURROUND_DIRECT_RIGHT] = { "SDR", "surround direct right" }, | ||
72 | [AV_CHAN_LOW_FREQUENCY_2 ] = { "LFE2", "low frequency 2" }, | ||
73 | [AV_CHAN_TOP_SIDE_LEFT ] = { "TSL", "top side left" }, | ||
74 | [AV_CHAN_TOP_SIDE_RIGHT ] = { "TSR", "top side right" }, | ||
75 | [AV_CHAN_BOTTOM_FRONT_CENTER ] = { "BFC", "bottom front center" }, | ||
76 | [AV_CHAN_BOTTOM_FRONT_LEFT ] = { "BFL", "bottom front left" }, | ||
77 | [AV_CHAN_BOTTOM_FRONT_RIGHT ] = { "BFR", "bottom front right" }, | ||
78 | [AV_CHAN_SIDE_SURROUND_LEFT ] = { "SSL", "side surround left" }, | ||
79 | [AV_CHAN_SIDE_SURROUND_RIGHT ] = { "SSR", "side surround right" }, | ||
80 | [AV_CHAN_TOP_SURROUND_LEFT ] = { "TTL", "top surround left" }, | ||
81 | [AV_CHAN_TOP_SURROUND_RIGHT ] = { "TTR", "top surround right" }, | ||
82 | [AV_CHAN_BINAURAL_LEFT ] = { "BIL", "binaural left" }, | ||
83 | [AV_CHAN_BINAURAL_RIGHT ] = { "BIR", "binaural right" }, | ||
84 | }; | ||
85 | |||
86 | 1752 | void av_channel_name_bprint(AVBPrint *bp, enum AVChannel channel_id) | |
87 | { | ||
88 |
3/4✓ Branch 0 taken 579 times.
✓ Branch 1 taken 1173 times.
✓ Branch 2 taken 579 times.
✗ Branch 3 not taken.
|
1752 | if (channel_id >= AV_CHAN_AMBISONIC_BASE && |
89 | channel_id <= AV_CHAN_AMBISONIC_END) | ||
90 | 579 | av_bprintf(bp, "AMBI%d", channel_id - AV_CHAN_AMBISONIC_BASE); | |
91 |
2/2✓ Branch 0 taken 1156 times.
✓ Branch 1 taken 17 times.
|
1173 | else if ((unsigned)channel_id < FF_ARRAY_ELEMS(channel_names) && |
92 |
1/2✓ Branch 0 taken 1156 times.
✗ Branch 1 not taken.
|
1156 | channel_names[channel_id].name) |
93 | 1156 | av_bprintf(bp, "%s", channel_names[channel_id].name); | |
94 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
|
17 | else if (channel_id == AV_CHAN_NONE) |
95 | ✗ | av_bprintf(bp, "NONE"); | |
96 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 12 times.
|
17 | else if (channel_id == AV_CHAN_UNKNOWN) |
97 | 5 | av_bprintf(bp, "UNK"); | |
98 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
|
12 | else if (channel_id == AV_CHAN_UNUSED) |
99 | 3 | av_bprintf(bp, "UNSD"); | |
100 | else | ||
101 | 9 | av_bprintf(bp, "USR%d", channel_id); | |
102 | 1752 | } | |
103 | |||
104 | 365 | int av_channel_name(char *buf, size_t buf_size, enum AVChannel channel_id) | |
105 | { | ||
106 | AVBPrint bp; | ||
107 | |||
108 |
3/4✓ Branch 0 taken 5 times.
✓ Branch 1 taken 360 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
|
365 | if (!buf && buf_size) |
109 | ✗ | return AVERROR(EINVAL); | |
110 | |||
111 | 365 | av_bprint_init_for_buffer(&bp, buf, buf_size); | |
112 | 365 | av_channel_name_bprint(&bp, channel_id); | |
113 | |||
114 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 365 times.
|
365 | if (bp.len >= INT_MAX) |
115 | ✗ | return AVERROR(ERANGE); | |
116 | 365 | return bp.len + 1; | |
117 | } | ||
118 | |||
119 | 15 | void av_channel_description_bprint(AVBPrint *bp, enum AVChannel channel_id) | |
120 | { | ||
121 |
3/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
15 | if (channel_id >= AV_CHAN_AMBISONIC_BASE && |
122 | channel_id <= AV_CHAN_AMBISONIC_END) | ||
123 | 6 | av_bprintf(bp, "ambisonic ACN %d", channel_id - AV_CHAN_AMBISONIC_BASE); | |
124 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3 times.
|
9 | else if ((unsigned)channel_id < FF_ARRAY_ELEMS(channel_names) && |
125 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | channel_names[channel_id].description) |
126 | 6 | av_bprintf(bp, "%s", channel_names[channel_id].description); | |
127 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | else if (channel_id == AV_CHAN_NONE) |
128 | ✗ | av_bprintf(bp, "none"); | |
129 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | else if (channel_id == AV_CHAN_UNKNOWN) |
130 | ✗ | av_bprintf(bp, "unknown"); | |
131 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | else if (channel_id == AV_CHAN_UNUSED) |
132 | ✗ | av_bprintf(bp, "unused"); | |
133 | else | ||
134 | 3 | av_bprintf(bp, "user %d", channel_id); | |
135 | 15 | } | |
136 | |||
137 | 10 | int av_channel_description(char *buf, size_t buf_size, enum AVChannel channel_id) | |
138 | { | ||
139 | AVBPrint bp; | ||
140 | |||
141 |
3/4✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
|
10 | if (!buf && buf_size) |
142 | ✗ | return AVERROR(EINVAL); | |
143 | |||
144 | 10 | av_bprint_init_for_buffer(&bp, buf, buf_size); | |
145 | 10 | av_channel_description_bprint(&bp, channel_id); | |
146 | |||
147 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (bp.len >= INT_MAX) |
148 | ✗ | return AVERROR(ERANGE); | |
149 | 10 | return bp.len + 1; | |
150 | } | ||
151 | |||
152 | 639 | enum AVChannel av_channel_from_string(const char *str) | |
153 | { | ||
154 | int i; | ||
155 | 639 | char *endptr = (char *)str; | |
156 | 639 | enum AVChannel id = AV_CHAN_NONE; | |
157 | |||
158 |
2/2✓ Branch 0 taken 260 times.
✓ Branch 1 taken 379 times.
|
639 | if (!strncmp(str, "AMBI", 4)) { |
159 | 260 | i = strtol(str + 4, NULL, 0); | |
160 |
3/4✓ Branch 0 taken 260 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 259 times.
|
260 | if (i < 0 || i > AV_CHAN_AMBISONIC_END - AV_CHAN_AMBISONIC_BASE) |
161 | 1 | return AV_CHAN_NONE; | |
162 | 259 | return AV_CHAN_AMBISONIC_BASE + i; | |
163 | } | ||
164 | |||
165 |
2/2✓ Branch 0 taken 8475 times.
✓ Branch 1 taken 119 times.
|
8594 | for (i = 0; i < FF_ARRAY_ELEMS(channel_names); i++) { |
166 |
4/4✓ Branch 0 taken 5218 times.
✓ Branch 1 taken 3257 times.
✓ Branch 2 taken 260 times.
✓ Branch 3 taken 4958 times.
|
8475 | if (channel_names[i].name && !strcmp(str, channel_names[i].name)) |
167 | 260 | return i; | |
168 | } | ||
169 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 118 times.
|
119 | if (!strcmp(str, "UNK")) |
170 | 1 | return AV_CHAN_UNKNOWN; | |
171 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 117 times.
|
118 | if (!strcmp(str, "UNSD")) |
172 | 1 | return AV_CHAN_UNUSED; | |
173 | |||
174 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 110 times.
|
117 | if (!strncmp(str, "USR", 3)) { |
175 | 7 | const char *p = str + 3; | |
176 | 7 | id = strtol(p, &endptr, 0); | |
177 | } | ||
178 |
3/4✓ Branch 0 taken 7 times.
✓ Branch 1 taken 110 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
117 | if (id >= 0 && !*endptr) |
179 | 7 | return id; | |
180 | |||
181 | 110 | return AV_CHAN_NONE; | |
182 | } | ||
183 | |||
184 | struct channel_layout_name { | ||
185 | const char *name; | ||
186 | AVChannelLayout layout; | ||
187 | }; | ||
188 | |||
189 | static const struct channel_layout_name channel_layout_map[] = { | ||
190 | { "mono", AV_CHANNEL_LAYOUT_MONO }, | ||
191 | { "stereo", AV_CHANNEL_LAYOUT_STEREO }, | ||
192 | { "2.1", AV_CHANNEL_LAYOUT_2POINT1 }, | ||
193 | { "3.0", AV_CHANNEL_LAYOUT_SURROUND }, | ||
194 | { "3.0(back)", AV_CHANNEL_LAYOUT_2_1 }, | ||
195 | { "4.0", AV_CHANNEL_LAYOUT_4POINT0 }, | ||
196 | { "quad", AV_CHANNEL_LAYOUT_QUAD }, | ||
197 | { "quad(side)", AV_CHANNEL_LAYOUT_2_2 }, | ||
198 | { "3.1", AV_CHANNEL_LAYOUT_3POINT1 }, | ||
199 | { "5.0", AV_CHANNEL_LAYOUT_5POINT0_BACK }, | ||
200 | { "5.0(side)", AV_CHANNEL_LAYOUT_5POINT0 }, | ||
201 | { "4.1", AV_CHANNEL_LAYOUT_4POINT1 }, | ||
202 | { "5.1", AV_CHANNEL_LAYOUT_5POINT1_BACK }, | ||
203 | { "5.1(side)", AV_CHANNEL_LAYOUT_5POINT1 }, | ||
204 | { "6.0", AV_CHANNEL_LAYOUT_6POINT0 }, | ||
205 | { "6.0(front)", AV_CHANNEL_LAYOUT_6POINT0_FRONT }, | ||
206 | { "3.1.2", AV_CHANNEL_LAYOUT_3POINT1POINT2 }, | ||
207 | { "hexagonal", AV_CHANNEL_LAYOUT_HEXAGONAL }, | ||
208 | { "6.1", AV_CHANNEL_LAYOUT_6POINT1 }, | ||
209 | { "6.1(back)", AV_CHANNEL_LAYOUT_6POINT1_BACK }, | ||
210 | { "6.1(front)", AV_CHANNEL_LAYOUT_6POINT1_FRONT }, | ||
211 | { "7.0", AV_CHANNEL_LAYOUT_7POINT0 }, | ||
212 | { "7.0(front)", AV_CHANNEL_LAYOUT_7POINT0_FRONT }, | ||
213 | { "7.1", AV_CHANNEL_LAYOUT_7POINT1 }, | ||
214 | { "7.1(wide)", AV_CHANNEL_LAYOUT_7POINT1_WIDE_BACK }, | ||
215 | { "7.1(wide-side)", AV_CHANNEL_LAYOUT_7POINT1_WIDE }, | ||
216 | { "5.1.2", AV_CHANNEL_LAYOUT_5POINT1POINT2 }, | ||
217 | { "5.1.2(back)", AV_CHANNEL_LAYOUT_5POINT1POINT2_BACK }, | ||
218 | { "octagonal", AV_CHANNEL_LAYOUT_OCTAGONAL }, | ||
219 | { "cube", AV_CHANNEL_LAYOUT_CUBE }, | ||
220 | { "5.1.4", AV_CHANNEL_LAYOUT_5POINT1POINT4_BACK }, | ||
221 | { "7.1.2", AV_CHANNEL_LAYOUT_7POINT1POINT2 }, | ||
222 | { "7.1.4", AV_CHANNEL_LAYOUT_7POINT1POINT4_BACK }, | ||
223 | { "7.2.3", AV_CHANNEL_LAYOUT_7POINT2POINT3 }, | ||
224 | { "9.1.4", AV_CHANNEL_LAYOUT_9POINT1POINT4_BACK }, | ||
225 | { "9.1.6", AV_CHANNEL_LAYOUT_9POINT1POINT6 }, | ||
226 | { "hexadecagonal", AV_CHANNEL_LAYOUT_HEXADECAGONAL }, | ||
227 | { "binaural", AV_CHANNEL_LAYOUT_BINAURAL }, | ||
228 | { "downmix", AV_CHANNEL_LAYOUT_STEREO_DOWNMIX, }, | ||
229 | { "22.2", AV_CHANNEL_LAYOUT_22POINT2, }, | ||
230 | }; | ||
231 | |||
232 | 54 | int av_channel_layout_custom_init(AVChannelLayout *channel_layout, int nb_channels) | |
233 | { | ||
234 | AVChannelCustom *map; | ||
235 | |||
236 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 54 times.
|
54 | if (nb_channels <= 0) |
237 | ✗ | return AVERROR(EINVAL); | |
238 | |||
239 | 54 | map = av_calloc(nb_channels, sizeof(*channel_layout->u.map)); | |
240 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 54 times.
|
54 | if (!map) |
241 | ✗ | return AVERROR(ENOMEM); | |
242 |
2/2✓ Branch 0 taken 117 times.
✓ Branch 1 taken 54 times.
|
171 | for (int i = 0; i < nb_channels; i++) |
243 | 117 | map[i].id = AV_CHAN_UNKNOWN; | |
244 | |||
245 | 54 | channel_layout->order = AV_CHANNEL_ORDER_CUSTOM; | |
246 | 54 | channel_layout->nb_channels = nb_channels; | |
247 | 54 | channel_layout->u.map = map; | |
248 | |||
249 | 54 | return 0; | |
250 | } | ||
251 | |||
252 | 24299 | int av_channel_layout_from_mask(AVChannelLayout *channel_layout, | |
253 | uint64_t mask) | ||
254 | { | ||
255 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 24259 times.
|
24299 | if (!mask) |
256 | 40 | return AVERROR(EINVAL); | |
257 | |||
258 | 24259 | channel_layout->order = AV_CHANNEL_ORDER_NATIVE; | |
259 | 24259 | channel_layout->nb_channels = av_popcount64(mask); | |
260 | 24259 | channel_layout->u.mask = mask; | |
261 | |||
262 | 24259 | return 0; | |
263 | } | ||
264 | |||
265 | 169 | static int parse_channel_list(AVChannelLayout *ch_layout, const char *str) | |
266 | { | ||
267 | int ret; | ||
268 | 169 | int nb_channels = 0; | |
269 | 169 | AVChannelCustom *map = NULL; | |
270 | 169 | AVChannelCustom custom = {0}; | |
271 | |||
272 |
2/2✓ Branch 0 taken 288 times.
✓ Branch 1 taken 72 times.
|
360 | while (*str) { |
273 | char *channel, *chname; | ||
274 | 288 | int ret = av_opt_get_key_value(&str, "@", "+", AV_OPT_FLAG_IMPLICIT_KEY, &channel, &chname); | |
275 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 288 times.
|
288 | if (ret < 0) { |
276 | ✗ | av_freep(&map); | |
277 | 97 | return ret; | |
278 | } | ||
279 |
2/2✓ Branch 0 taken 121 times.
✓ Branch 1 taken 167 times.
|
288 | if (*str) |
280 | 121 | str++; // skip separator | |
281 |
2/2✓ Branch 0 taken 274 times.
✓ Branch 1 taken 14 times.
|
288 | if (!channel) { |
282 | 274 | channel = chname; | |
283 | 274 | chname = NULL; | |
284 | } | ||
285 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 274 times.
|
288 | av_strlcpy(custom.name, chname ? chname : "", sizeof(custom.name)); |
286 | 288 | custom.id = av_channel_from_string(channel); | |
287 | 288 | av_free(channel); | |
288 | 288 | av_free(chname); | |
289 |
2/2✓ Branch 0 taken 97 times.
✓ Branch 1 taken 191 times.
|
288 | if (custom.id == AV_CHAN_NONE) { |
290 | 97 | av_freep(&map); | |
291 | 97 | return AVERROR(EINVAL); | |
292 | } | ||
293 | |||
294 | 191 | av_dynarray2_add((void **)&map, &nb_channels, sizeof(custom), (void *)&custom); | |
295 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 191 times.
|
191 | if (!map) |
296 | ✗ | return AVERROR(ENOMEM); | |
297 | } | ||
298 | |||
299 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 71 times.
|
72 | if (!nb_channels) |
300 | 1 | return AVERROR(EINVAL); | |
301 | |||
302 | 71 | ch_layout->order = AV_CHANNEL_ORDER_CUSTOM; | |
303 | 71 | ch_layout->u.map = map; | |
304 | 71 | ch_layout->nb_channels = nb_channels; | |
305 | |||
306 | 71 | ret = av_channel_layout_retype(ch_layout, 0, AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL); | |
307 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 71 times.
|
71 | av_assert0(ret == 0); |
308 | |||
309 | 71 | return 0; | |
310 | } | ||
311 | |||
312 | 2092 | int av_channel_layout_from_string(AVChannelLayout *channel_layout, | |
313 | const char *str) | ||
314 | { | ||
315 | int i, matches, ret; | ||
316 | 2092 | int channels = 0, nb_channels = 0; | |
317 | char *chlist, *end; | ||
318 | 2092 | uint64_t mask = 0; | |
319 | |||
320 | /* channel layout names */ | ||
321 |
2/2✓ Branch 0 taken 15738 times.
✓ Branch 1 taken 182 times.
|
15920 | for (i = 0; i < FF_ARRAY_ELEMS(channel_layout_map); i++) { |
322 |
3/4✓ Branch 0 taken 15738 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1910 times.
✓ Branch 3 taken 13828 times.
|
15738 | if (channel_layout_map[i].name && !strcmp(str, channel_layout_map[i].name)) { |
323 | 1910 | *channel_layout = channel_layout_map[i].layout; | |
324 | 1910 | return 0; | |
325 | } | ||
326 | } | ||
327 | |||
328 | /* This function is a channel layout initializer, so we have to | ||
329 | * zero-initialize before we start setting fields individually. */ | ||
330 | 182 | memset(channel_layout, 0, sizeof(*channel_layout)); | |
331 | |||
332 | /* ambisonic */ | ||
333 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 169 times.
|
182 | if (!strncmp(str, "ambisonic ", 10)) { |
334 | 13 | const char *p = str + 10; | |
335 | char *endptr; | ||
336 | 13 | AVChannelLayout extra = {0}; | |
337 | int order; | ||
338 | |||
339 | 13 | order = strtol(p, &endptr, 0); | |
340 |
2/4✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
|
13 | if (order < 0 || order + 1 > INT_MAX / (order + 1) || |
341 |
3/4✓ Branch 0 taken 10 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
|
13 | (*endptr && *endptr != '+')) |
342 | ✗ | return AVERROR(EINVAL); | |
343 | |||
344 | 13 | channel_layout->order = AV_CHANNEL_ORDER_AMBISONIC; | |
345 | 13 | channel_layout->nb_channels = (order + 1) * (order + 1); | |
346 | |||
347 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 3 times.
|
13 | if (*endptr) { |
348 | 10 | int ret = av_channel_layout_from_string(&extra, endptr + 1); | |
349 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (ret < 0) |
350 | ✗ | return ret; | |
351 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (extra.nb_channels >= INT_MAX - channel_layout->nb_channels) { |
352 | ✗ | av_channel_layout_uninit(&extra); | |
353 | ✗ | return AVERROR(EINVAL); | |
354 | } | ||
355 | |||
356 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
|
10 | if (extra.order == AV_CHANNEL_ORDER_NATIVE) { |
357 | 8 | channel_layout->u.mask = extra.u.mask; | |
358 | } else { | ||
359 | 2 | channel_layout->order = AV_CHANNEL_ORDER_CUSTOM; | |
360 | 2 | channel_layout->u.map = | |
361 | 2 | av_calloc(channel_layout->nb_channels + extra.nb_channels, | |
362 | sizeof(*channel_layout->u.map)); | ||
363 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!channel_layout->u.map) { |
364 | ✗ | av_channel_layout_uninit(&extra); | |
365 | ✗ | return AVERROR(ENOMEM); | |
366 | } | ||
367 | |||
368 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 2 times.
|
15 | for (i = 0; i < channel_layout->nb_channels; i++) |
369 | 13 | channel_layout->u.map[i].id = AV_CHAN_AMBISONIC_BASE + i; | |
370 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
|
5 | for (i = 0; i < extra.nb_channels; i++) { |
371 | 3 | enum AVChannel ch = av_channel_layout_channel_from_index(&extra, i); | |
372 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
3 | if (CHAN_IS_AMBI(ch)) { |
373 | ✗ | av_channel_layout_uninit(channel_layout); | |
374 | ✗ | av_channel_layout_uninit(&extra); | |
375 | ✗ | return AVERROR(EINVAL); | |
376 | } | ||
377 | 3 | channel_layout->u.map[channel_layout->nb_channels + i].id = ch; | |
378 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (extra.order == AV_CHANNEL_ORDER_CUSTOM && |
379 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
|
3 | extra.u.map[i].name[0]) |
380 | 1 | av_strlcpy(channel_layout->u.map[channel_layout->nb_channels + i].name, | |
381 | 1 | extra.u.map[i].name, | |
382 | sizeof(channel_layout->u.map[channel_layout->nb_channels + i].name)); | ||
383 | } | ||
384 | } | ||
385 | 10 | channel_layout->nb_channels += extra.nb_channels; | |
386 | 10 | av_channel_layout_uninit(&extra); | |
387 | } | ||
388 | |||
389 | 13 | return 0; | |
390 | } | ||
391 | |||
392 | 169 | chlist = av_strdup(str); | |
393 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 169 times.
|
169 | if (!chlist) |
394 | ✗ | return AVERROR(ENOMEM); | |
395 | |||
396 | /* channel names */ | ||
397 | 169 | matches = av_sscanf(str, "%d channels (%[^)]", &nb_channels, chlist); | |
398 | 169 | ret = parse_channel_list(channel_layout, chlist); | |
399 | 169 | av_freep(&chlist); | |
400 |
3/4✓ Branch 0 taken 98 times.
✓ Branch 1 taken 71 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 98 times.
|
169 | if (ret < 0 && ret != AVERROR(EINVAL)) |
401 | ✗ | return ret; | |
402 | |||
403 |
2/2✓ Branch 0 taken 71 times.
✓ Branch 1 taken 98 times.
|
169 | if (ret >= 0) { |
404 | 71 | end = strchr(str, ')'); | |
405 |
7/8✓ Branch 0 taken 31 times.
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 29 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 28 times.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 28 times.
|
71 | if (matches == 2 && (nb_channels != channel_layout->nb_channels || !end || *++end)) { |
406 | 3 | av_channel_layout_uninit(channel_layout); | |
407 | 3 | return AVERROR(EINVAL); | |
408 | } | ||
409 | 68 | return 0; | |
410 | } | ||
411 | |||
412 | 98 | errno = 0; | |
413 | 98 | mask = strtoull(str, &end, 0); | |
414 | |||
415 | /* channel layout mask */ | ||
416 |
7/8✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 45 times.
✓ Branch 3 taken 53 times.
✓ Branch 4 taken 44 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 43 times.
✓ Branch 7 taken 1 times.
|
98 | if (!errno && !*end && !strchr(str, '-') && mask) { |
417 | 43 | av_channel_layout_from_mask(channel_layout, mask); | |
418 | 43 | return 0; | |
419 | } | ||
420 | |||
421 | 55 | errno = 0; | |
422 | 55 | channels = strtol(str, &end, 10); | |
423 | |||
424 | /* number of channels */ | ||
425 |
5/6✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 43 times.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 1 times.
|
55 | if (!errno && !strcmp(end, "c") && channels > 0) { |
426 | 11 | av_channel_layout_default(channel_layout, channels); | |
427 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
|
11 | if (channel_layout->order == AV_CHANNEL_ORDER_NATIVE) |
428 | 9 | return 0; | |
429 | } | ||
430 | |||
431 | /* number of unordered channels */ | ||
432 |
5/6✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 24 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 21 times.
|
46 | if (!errno && (!strcmp(end, "C") || !strcmp(end, " channels")) |
433 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | && channels > 0) { |
434 | 25 | channel_layout->order = AV_CHANNEL_ORDER_UNSPEC; | |
435 | 25 | channel_layout->nb_channels = channels; | |
436 | 25 | return 0; | |
437 | } | ||
438 | |||
439 | 21 | return AVERROR(EINVAL); | |
440 | } | ||
441 | |||
442 | 10320125 | void av_channel_layout_uninit(AVChannelLayout *channel_layout) | |
443 | { | ||
444 |
2/2✓ Branch 0 taken 1630 times.
✓ Branch 1 taken 10318495 times.
|
10320125 | if (channel_layout->order == AV_CHANNEL_ORDER_CUSTOM) |
445 | 1630 | av_freep(&channel_layout->u.map); | |
446 | 10320125 | memset(channel_layout, 0, sizeof(*channel_layout)); | |
447 | 10320125 | } | |
448 | |||
449 | 2335107 | int av_channel_layout_copy(AVChannelLayout *dst, const AVChannelLayout *src) | |
450 | { | ||
451 | 2335107 | av_channel_layout_uninit(dst); | |
452 | 2335107 | *dst = *src; | |
453 |
2/2✓ Branch 0 taken 1431 times.
✓ Branch 1 taken 2333676 times.
|
2335107 | if (src->order == AV_CHANNEL_ORDER_CUSTOM) { |
454 | 1431 | dst->u.map = av_malloc_array(src->nb_channels, sizeof(*dst->u.map)); | |
455 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1431 times.
|
1431 | if (!dst->u.map) |
456 | ✗ | return AVERROR(ENOMEM); | |
457 | 1431 | memcpy(dst->u.map, src->u.map, src->nb_channels * sizeof(*src->u.map)); | |
458 | } | ||
459 | 2335107 | return 0; | |
460 | } | ||
461 | |||
462 | 709 | static int64_t masked_description(const AVChannelLayout *channel_layout, int start_channel) | |
463 | { | ||
464 | 709 | uint64_t mask = 0; | |
465 |
2/2✓ Branch 0 taken 1040 times.
✓ Branch 1 taken 223 times.
|
1263 | for (int i = start_channel; i < channel_layout->nb_channels; i++) { |
466 | 1040 | enum AVChannel ch = channel_layout->u.map[i].id; | |
467 |
5/6✓ Branch 0 taken 1040 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 585 times.
✓ Branch 3 taken 455 times.
✓ Branch 4 taken 554 times.
✓ Branch 5 taken 31 times.
|
1040 | if (ch >= 0 && ch < 63 && mask < (1ULL << ch)) |
468 | 554 | mask |= (1ULL << ch); | |
469 | else | ||
470 | 486 | return AVERROR(EINVAL); | |
471 | } | ||
472 | 223 | return mask; | |
473 | } | ||
474 | |||
475 | 725 | static int has_channel_names(const AVChannelLayout *channel_layout) | |
476 | { | ||
477 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 725 times.
|
725 | if (channel_layout->order != AV_CHANNEL_ORDER_CUSTOM) |
478 | ✗ | return 0; | |
479 |
2/2✓ Branch 0 taken 1131 times.
✓ Branch 1 taken 706 times.
|
1837 | for (int i = 0; i < channel_layout->nb_channels; i++) |
480 |
2/2✓ Branch 0 taken 19 times.
✓ Branch 1 taken 1112 times.
|
1131 | if (channel_layout->u.map[i].name[0]) |
481 | 19 | return 1; | |
482 | 706 | return 0; | |
483 | } | ||
484 | |||
485 | 526 | int av_channel_layout_ambisonic_order(const AVChannelLayout *channel_layout) | |
486 | { | ||
487 | int i, highest_ambi, order; | ||
488 | |||
489 |
2/2✓ Branch 0 taken 510 times.
✓ Branch 1 taken 16 times.
|
526 | if (channel_layout->order != AV_CHANNEL_ORDER_AMBISONIC && |
490 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 510 times.
|
510 | channel_layout->order != AV_CHANNEL_ORDER_CUSTOM) |
491 | ✗ | return AVERROR(EINVAL); | |
492 | |||
493 | 526 | highest_ambi = -1; | |
494 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 510 times.
|
526 | if (channel_layout->order == AV_CHANNEL_ORDER_AMBISONIC) |
495 | 16 | highest_ambi = channel_layout->nb_channels - av_popcount64(channel_layout->u.mask) - 1; | |
496 | else { | ||
497 | 510 | const AVChannelCustom *map = channel_layout->u.map; | |
498 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 510 times.
|
510 | av_assert0(channel_layout->order == AV_CHANNEL_ORDER_CUSTOM); |
499 | |||
500 |
2/2✓ Branch 0 taken 660 times.
✓ Branch 1 taken 65 times.
|
725 | for (i = 0; i < channel_layout->nb_channels; i++) { |
501 |
3/4✓ Branch 0 taken 519 times.
✓ Branch 1 taken 141 times.
✓ Branch 2 taken 519 times.
✗ Branch 3 not taken.
|
660 | int is_ambi = CHAN_IS_AMBI(map[i].id); |
502 | |||
503 | /* ambisonic following non-ambisonic */ | ||
504 |
6/8✓ Branch 0 taken 150 times.
✓ Branch 1 taken 510 times.
✓ Branch 2 taken 59 times.
✓ Branch 3 taken 91 times.
✓ Branch 4 taken 59 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 59 times.
|
660 | if (i > 0 && is_ambi && !CHAN_IS_AMBI(map[i - 1].id)) |
505 | ✗ | return AVERROR(EINVAL); | |
506 | |||
507 | /* non-default ordering */ | ||
508 |
4/4✓ Branch 0 taken 519 times.
✓ Branch 1 taken 141 times.
✓ Branch 2 taken 445 times.
✓ Branch 3 taken 74 times.
|
660 | if (is_ambi && map[i].id - AV_CHAN_AMBISONIC_BASE != i) |
509 | 445 | return AVERROR(EINVAL); | |
510 | |||
511 |
3/4✓ Branch 0 taken 74 times.
✓ Branch 1 taken 141 times.
✓ Branch 2 taken 74 times.
✗ Branch 3 not taken.
|
215 | if (CHAN_IS_AMBI(map[i].id)) |
512 | 74 | highest_ambi = i; | |
513 | } | ||
514 | } | ||
515 | /* no ambisonic channels*/ | ||
516 |
2/2✓ Branch 0 taken 50 times.
✓ Branch 1 taken 31 times.
|
81 | if (highest_ambi < 0) |
517 | 50 | return AVERROR(EINVAL); | |
518 | |||
519 | 31 | order = floor(sqrt(highest_ambi)); | |
520 | /* incomplete order - some harmonics are missing */ | ||
521 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if ((order + 1) * (order + 1) != highest_ambi + 1) |
522 | ✗ | return AVERROR(EINVAL); | |
523 | |||
524 | 31 | return order; | |
525 | } | ||
526 | |||
527 | 307 | static enum AVChannelOrder canonical_order(AVChannelLayout *channel_layout) | |
528 | { | ||
529 | 307 | int has_known_channel = 0; | |
530 | int order; | ||
531 | |||
532 |
2/2✓ Branch 0 taken 117 times.
✓ Branch 1 taken 190 times.
|
307 | if (channel_layout->order != AV_CHANNEL_ORDER_CUSTOM) |
533 | 117 | return channel_layout->order; | |
534 | |||
535 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 186 times.
|
190 | if (has_channel_names(channel_layout)) |
536 | 4 | return AV_CHANNEL_ORDER_CUSTOM; | |
537 | |||
538 |
4/4✓ Branch 0 taken 270 times.
✓ Branch 1 taken 103 times.
✓ Branch 2 taken 187 times.
✓ Branch 3 taken 83 times.
|
373 | for (int i = 0; i < channel_layout->nb_channels && !has_known_channel; i++) |
539 |
2/2✓ Branch 0 taken 186 times.
✓ Branch 1 taken 1 times.
|
187 | if (channel_layout->u.map[i].id != AV_CHAN_UNKNOWN) |
540 | 186 | has_known_channel = 1; | |
541 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 186 times.
|
186 | if (!has_known_channel) |
542 | ✗ | return AV_CHANNEL_ORDER_UNSPEC; | |
543 | |||
544 |
2/2✓ Branch 1 taken 109 times.
✓ Branch 2 taken 77 times.
|
186 | if (masked_description(channel_layout, 0) > 0) |
545 | 109 | return AV_CHANNEL_ORDER_NATIVE; | |
546 | |||
547 | 77 | order = av_channel_layout_ambisonic_order(channel_layout); | |
548 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 76 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
77 | if (order >= 0 && masked_description(channel_layout, (order + 1) * (order + 1)) >= 0) |
549 | 1 | return AV_CHANNEL_ORDER_AMBISONIC; | |
550 | |||
551 | 76 | return AV_CHANNEL_ORDER_CUSTOM; | |
552 | } | ||
553 | |||
554 | /** | ||
555 | * If the custom layout is n-th order standard-order ambisonic, with optional | ||
556 | * extra non-diegetic channels at the end, write its string description in bp. | ||
557 | * Return a negative error code otherwise. | ||
558 | */ | ||
559 | 446 | static int try_describe_ambisonic(AVBPrint *bp, const AVChannelLayout *channel_layout) | |
560 | { | ||
561 | int nb_ambi_channels; | ||
562 | 446 | int order = av_channel_layout_ambisonic_order(channel_layout); | |
563 |
2/2✓ Branch 0 taken 417 times.
✓ Branch 1 taken 29 times.
|
446 | if (order < 0) |
564 | 417 | return order; | |
565 | |||
566 | 29 | av_bprintf(bp, "ambisonic %d", order); | |
567 | |||
568 | /* extra channels present */ | ||
569 | 29 | nb_ambi_channels = (order + 1) * (order + 1); | |
570 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 17 times.
|
29 | if (nb_ambi_channels < channel_layout->nb_channels) { |
571 | 12 | AVChannelLayout extra = { 0 }; | |
572 | |||
573 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 7 times.
|
12 | if (channel_layout->order == AV_CHANNEL_ORDER_AMBISONIC) { |
574 | 5 | extra.order = AV_CHANNEL_ORDER_NATIVE; | |
575 | 5 | extra.nb_channels = av_popcount64(channel_layout->u.mask); | |
576 | 5 | extra.u.mask = channel_layout->u.mask; | |
577 | } else { | ||
578 | int64_t mask; | ||
579 |
4/4✓ Branch 1 taken 4 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 3 times.
|
11 | if (!has_channel_names(channel_layout) && |
580 | 4 | (mask = masked_description(channel_layout, nb_ambi_channels)) > 0) { | |
581 | 1 | extra.order = AV_CHANNEL_ORDER_NATIVE; | |
582 | 1 | extra.nb_channels = av_popcount64(mask); | |
583 | 1 | extra.u.mask = mask; | |
584 | } else { | ||
585 | 6 | extra.order = AV_CHANNEL_ORDER_CUSTOM; | |
586 | 6 | extra.nb_channels = channel_layout->nb_channels - nb_ambi_channels; | |
587 | 6 | extra.u.map = channel_layout->u.map + nb_ambi_channels; | |
588 | } | ||
589 | } | ||
590 | |||
591 | 12 | av_bprint_chars(bp, '+', 1); | |
592 | 12 | av_channel_layout_describe_bprint(&extra, bp); | |
593 | /* Not calling uninit here on extra because we don't own the u.map pointer */ | ||
594 | } | ||
595 | |||
596 | 29 | return 0; | |
597 | } | ||
598 | |||
599 | 16831 | int av_channel_layout_describe_bprint(const AVChannelLayout *channel_layout, | |
600 | AVBPrint *bp) | ||
601 | { | ||
602 | int i; | ||
603 | |||
604 |
4/5✓ Branch 0 taken 16267 times.
✓ Branch 1 taken 430 times.
✓ Branch 2 taken 118 times.
✓ Branch 3 taken 16 times.
✗ Branch 4 not taken.
|
16831 | switch (channel_layout->order) { |
605 | 16267 | case AV_CHANNEL_ORDER_NATIVE: | |
606 |
2/2✓ Branch 0 taken 57590 times.
✓ Branch 1 taken 181 times.
|
57771 | for (i = 0; i < FF_ARRAY_ELEMS(channel_layout_map); i++) |
607 |
2/2✓ Branch 0 taken 16086 times.
✓ Branch 1 taken 41504 times.
|
57590 | if (channel_layout->u.mask == channel_layout_map[i].layout.u.mask) { |
608 | 16086 | av_bprintf(bp, "%s", channel_layout_map[i].name); | |
609 | 16086 | return 0; | |
610 | } | ||
611 | // fall-through | ||
612 | case AV_CHANNEL_ORDER_CUSTOM: | ||
613 |
2/2✓ Branch 0 taken 430 times.
✓ Branch 1 taken 181 times.
|
611 | if (channel_layout->order == AV_CHANNEL_ORDER_CUSTOM) { |
614 | int64_t mask; | ||
615 | 430 | int res = try_describe_ambisonic(bp, channel_layout); | |
616 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 417 times.
|
430 | if (res >= 0) |
617 | 13 | return 0; | |
618 |
4/4✓ Branch 1 taken 406 times.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 405 times.
|
823 | if (!has_channel_names(channel_layout) && |
619 | 406 | (mask = masked_description(channel_layout, 0)) > 0) { | |
620 | 1 | AVChannelLayout native = { .order = AV_CHANNEL_ORDER_NATIVE, | |
621 | 1 | .nb_channels = av_popcount64(mask), | |
622 | .u.mask = mask }; | ||
623 | 1 | return av_channel_layout_describe_bprint(&native, bp); | |
624 | } | ||
625 | } | ||
626 |
1/2✓ Branch 0 taken 597 times.
✗ Branch 1 not taken.
|
597 | if (channel_layout->nb_channels) |
627 | 597 | av_bprintf(bp, "%d channels (", channel_layout->nb_channels); | |
628 |
2/2✓ Branch 0 taken 1094 times.
✓ Branch 1 taken 597 times.
|
1691 | for (i = 0; i < channel_layout->nb_channels; i++) { |
629 | 1094 | enum AVChannel ch = av_channel_layout_channel_from_index(channel_layout, i); | |
630 | |||
631 |
2/2✓ Branch 0 taken 497 times.
✓ Branch 1 taken 597 times.
|
1094 | if (i) |
632 | 497 | av_bprintf(bp, "+"); | |
633 | 1094 | av_channel_name_bprint(bp, ch); | |
634 |
2/2✓ Branch 0 taken 473 times.
✓ Branch 1 taken 621 times.
|
1094 | if (channel_layout->order == AV_CHANNEL_ORDER_CUSTOM && |
635 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 456 times.
|
473 | channel_layout->u.map[i].name[0]) |
636 | 17 | av_bprintf(bp, "@%s", channel_layout->u.map[i].name); | |
637 | } | ||
638 |
1/2✓ Branch 0 taken 597 times.
✗ Branch 1 not taken.
|
597 | if (channel_layout->nb_channels) { |
639 | 597 | av_bprintf(bp, ")"); | |
640 | 597 | return 0; | |
641 | } | ||
642 | // fall-through | ||
643 | case AV_CHANNEL_ORDER_UNSPEC: | ||
644 | 118 | av_bprintf(bp, "%d channels", channel_layout->nb_channels); | |
645 | 118 | return 0; | |
646 | 16 | case AV_CHANNEL_ORDER_AMBISONIC: | |
647 | 16 | return try_describe_ambisonic(bp, channel_layout); | |
648 | ✗ | default: | |
649 | ✗ | return AVERROR(EINVAL); | |
650 | } | ||
651 | } | ||
652 | |||
653 | 11462 | int av_channel_layout_describe(const AVChannelLayout *channel_layout, | |
654 | char *buf, size_t buf_size) | ||
655 | { | ||
656 | AVBPrint bp; | ||
657 | int ret; | ||
658 | |||
659 |
3/4✓ Branch 0 taken 22 times.
✓ Branch 1 taken 11440 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
|
11462 | if (!buf && buf_size) |
660 | ✗ | return AVERROR(EINVAL); | |
661 | |||
662 | 11462 | av_bprint_init_for_buffer(&bp, buf, buf_size); | |
663 | 11462 | ret = av_channel_layout_describe_bprint(channel_layout, &bp); | |
664 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11462 times.
|
11462 | if (ret < 0) |
665 | ✗ | return ret; | |
666 | |||
667 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11462 times.
|
11462 | if (bp.len >= INT_MAX) |
668 | ✗ | return AVERROR(ERANGE); | |
669 | 11462 | return bp.len + 1; | |
670 | } | ||
671 | |||
672 | enum AVChannel | ||
673 | 3238 | av_channel_layout_channel_from_index(const AVChannelLayout *channel_layout, | |
674 | unsigned int idx) | ||
675 | { | ||
676 | int i; | ||
677 | |||
678 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 3217 times.
|
3238 | if (idx >= channel_layout->nb_channels) |
679 | 21 | return AV_CHAN_NONE; | |
680 | |||
681 |
3/4✓ Branch 0 taken 1922 times.
✓ Branch 1 taken 344 times.
✓ Branch 2 taken 951 times.
✗ Branch 3 not taken.
|
3217 | switch (channel_layout->order) { |
682 | 1922 | case AV_CHANNEL_ORDER_CUSTOM: | |
683 | 1922 | return channel_layout->u.map[idx].id; | |
684 | 344 | case AV_CHANNEL_ORDER_AMBISONIC: { | |
685 | 344 | int ambi_channels = channel_layout->nb_channels - av_popcount64(channel_layout->u.mask); | |
686 |
2/2✓ Branch 0 taken 330 times.
✓ Branch 1 taken 14 times.
|
344 | if (idx < ambi_channels) |
687 | 330 | return AV_CHAN_AMBISONIC_BASE + idx; | |
688 | 14 | idx -= ambi_channels; | |
689 | } | ||
690 | // fall-through | ||
691 | 965 | case AV_CHANNEL_ORDER_NATIVE: | |
692 |
1/2✓ Branch 0 taken 5762 times.
✗ Branch 1 not taken.
|
5762 | for (i = 0; i < 64; i++) { |
693 |
4/4✓ Branch 0 taken 2446 times.
✓ Branch 1 taken 3316 times.
✓ Branch 2 taken 965 times.
✓ Branch 3 taken 1481 times.
|
5762 | if ((1ULL << i) & channel_layout->u.mask && !idx--) |
694 | 965 | return i; | |
695 | } | ||
696 | default: | ||
697 | ✗ | return AV_CHAN_NONE; | |
698 | } | ||
699 | } | ||
700 | |||
701 | enum AVChannel | ||
702 | 19 | av_channel_layout_channel_from_string(const AVChannelLayout *channel_layout, | |
703 | const char *str) | ||
704 | { | ||
705 | 19 | int index = av_channel_layout_index_from_string(channel_layout, str); | |
706 | |||
707 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 12 times.
|
19 | if (index < 0) |
708 | 7 | return AV_CHAN_NONE; | |
709 | |||
710 | 12 | return av_channel_layout_channel_from_index(channel_layout, index); | |
711 | } | ||
712 | |||
713 | 10926 | int av_channel_layout_index_from_channel(const AVChannelLayout *channel_layout, | |
714 | enum AVChannel channel) | ||
715 | { | ||
716 | int i; | ||
717 | |||
718 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10926 times.
|
10926 | if (channel == AV_CHAN_NONE) |
719 | ✗ | return AVERROR(EINVAL); | |
720 | |||
721 |
2/3✓ Branch 0 taken 34 times.
✓ Branch 1 taken 10892 times.
✗ Branch 2 not taken.
|
10926 | switch (channel_layout->order) { |
722 | 34 | case AV_CHANNEL_ORDER_CUSTOM: | |
723 |
2/2✓ Branch 0 taken 103 times.
✓ Branch 1 taken 21 times.
|
124 | for (i = 0; i < channel_layout->nb_channels; i++) |
724 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 90 times.
|
103 | if (channel_layout->u.map[i].id == channel) |
725 | 13 | return i; | |
726 | 21 | return AVERROR(EINVAL); | |
727 | 10892 | case AV_CHANNEL_ORDER_AMBISONIC: | |
728 | case AV_CHANNEL_ORDER_NATIVE: { | ||
729 | 10892 | uint64_t mask = channel_layout->u.mask; | |
730 | 10892 | int ambi_channels = channel_layout->nb_channels - av_popcount64(mask); | |
731 |
4/4✓ Branch 0 taken 202 times.
✓ Branch 1 taken 10690 times.
✓ Branch 2 taken 193 times.
✓ Branch 3 taken 9 times.
|
10892 | if (channel_layout->order == AV_CHANNEL_ORDER_AMBISONIC && |
732 | channel >= AV_CHAN_AMBISONIC_BASE) { | ||
733 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 193 times.
|
193 | if (channel - AV_CHAN_AMBISONIC_BASE >= ambi_channels) |
734 | ✗ | return AVERROR(EINVAL); | |
735 | 193 | return channel - AV_CHAN_AMBISONIC_BASE; | |
736 | } | ||
737 |
3/4✓ Branch 0 taken 10699 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5460 times.
✓ Branch 3 taken 5239 times.
|
10699 | if ((unsigned)channel > 63 || !(mask & (1ULL << channel))) |
738 | 5460 | return AVERROR(EINVAL); | |
739 | 5239 | mask &= (1ULL << channel) - 1; | |
740 | 5239 | return av_popcount64(mask) + ambi_channels; | |
741 | } | ||
742 | ✗ | default: | |
743 | ✗ | return AVERROR(EINVAL); | |
744 | } | ||
745 | } | ||
746 | |||
747 | 35 | int av_channel_layout_index_from_string(const AVChannelLayout *channel_layout, | |
748 | const char *str) | ||
749 | { | ||
750 | char *chname; | ||
751 | 35 | enum AVChannel ch = AV_CHAN_NONE; | |
752 | |||
753 |
2/3✓ Branch 0 taken 18 times.
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
|
35 | switch (channel_layout->order) { |
754 | 18 | case AV_CHANNEL_ORDER_CUSTOM: | |
755 | 18 | chname = strstr(str, "@"); | |
756 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 10 times.
|
18 | if (chname) { |
757 | char buf[16]; | ||
758 | 8 | chname++; | |
759 | 8 | av_strlcpy(buf, str, FFMIN(sizeof(buf), chname - str)); | |
760 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (!*chname) |
761 | ✗ | chname = NULL; | |
762 | 8 | ch = av_channel_from_string(buf); | |
763 |
3/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
8 | if (ch == AV_CHAN_NONE && *buf) |
764 | ✗ | return AVERROR(EINVAL); | |
765 | } | ||
766 |
4/4✓ Branch 0 taken 22 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 2 times.
|
32 | for (int i = 0; chname && i < channel_layout->nb_channels; i++) { |
767 |
4/4✓ Branch 0 taken 12 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 2 times.
|
20 | if (!strcmp(chname, channel_layout->u.map[i].name) && |
768 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
|
10 | (ch == AV_CHAN_NONE || ch == channel_layout->u.map[i].id)) |
769 | 6 | return i; | |
770 | } | ||
771 | // fall-through | ||
772 | case AV_CHANNEL_ORDER_AMBISONIC: | ||
773 | case AV_CHANNEL_ORDER_NATIVE: | ||
774 | 29 | ch = av_channel_from_string(str); | |
775 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 22 times.
|
29 | if (ch == AV_CHAN_NONE) |
776 | 7 | return AVERROR(EINVAL); | |
777 | 22 | return av_channel_layout_index_from_channel(channel_layout, ch); | |
778 | } | ||
779 | |||
780 | ✗ | return AVERROR(EINVAL); | |
781 | } | ||
782 | |||
783 | 300309 | int av_channel_layout_check(const AVChannelLayout *channel_layout) | |
784 | { | ||
785 |
2/2✓ Branch 0 taken 6565 times.
✓ Branch 1 taken 293744 times.
|
300309 | if (channel_layout->nb_channels <= 0) |
786 | 6565 | return 0; | |
787 | |||
788 |
4/5✓ Branch 0 taken 288674 times.
✓ Branch 1 taken 405 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 4655 times.
✗ Branch 4 not taken.
|
293744 | switch (channel_layout->order) { |
789 | 288674 | case AV_CHANNEL_ORDER_NATIVE: | |
790 | 288674 | return av_popcount64(channel_layout->u.mask) == channel_layout->nb_channels; | |
791 | 405 | case AV_CHANNEL_ORDER_CUSTOM: | |
792 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 405 times.
|
405 | if (!channel_layout->u.map) |
793 | ✗ | return 0; | |
794 |
2/2✓ Branch 0 taken 620 times.
✓ Branch 1 taken 405 times.
|
1025 | for (int i = 0; i < channel_layout->nb_channels; i++) { |
795 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 620 times.
|
620 | if (channel_layout->u.map[i].id == AV_CHAN_NONE) |
796 | ✗ | return 0; | |
797 | } | ||
798 | 405 | return 1; | |
799 | 10 | case AV_CHANNEL_ORDER_AMBISONIC: | |
800 | /* If non-diegetic channels are present, ensure they are taken into account */ | ||
801 | 10 | return av_popcount64(channel_layout->u.mask) < channel_layout->nb_channels; | |
802 | 4655 | case AV_CHANNEL_ORDER_UNSPEC: | |
803 | 4655 | return 1; | |
804 | ✗ | default: | |
805 | ✗ | return 0; | |
806 | } | ||
807 | } | ||
808 | |||
809 | 1448194 | int av_channel_layout_compare(const AVChannelLayout *chl, const AVChannelLayout *chl1) | |
810 | { | ||
811 | int i; | ||
812 | |||
813 | /* different channel counts -> not equal */ | ||
814 |
2/2✓ Branch 0 taken 7147 times.
✓ Branch 1 taken 1441047 times.
|
1448194 | if (chl->nb_channels != chl1->nb_channels) |
815 | 7147 | return 1; | |
816 | |||
817 | /* if only one is unspecified -> not equal */ | ||
818 | 1441047 | if ((chl->order == AV_CHANNEL_ORDER_UNSPEC) != | |
819 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1441045 times.
|
1441047 | (chl1->order == AV_CHANNEL_ORDER_UNSPEC)) |
820 | 2 | return 1; | |
821 | /* both are unspecified -> equal */ | ||
822 |
2/2✓ Branch 0 taken 10269 times.
✓ Branch 1 taken 1430776 times.
|
1441045 | else if (chl->order == AV_CHANNEL_ORDER_UNSPEC) |
823 | 10269 | return 0; | |
824 | |||
825 | /* can compare masks directly */ | ||
826 |
2/2✓ Branch 0 taken 462 times.
✓ Branch 1 taken 1430314 times.
|
1430776 | if ((chl->order == AV_CHANNEL_ORDER_NATIVE || |
827 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 450 times.
|
462 | chl->order == AV_CHANNEL_ORDER_AMBISONIC) && |
828 |
1/2✓ Branch 0 taken 1430326 times.
✗ Branch 1 not taken.
|
1430326 | chl->order == chl1->order) |
829 | 1430326 | return chl->u.mask != chl1->u.mask; | |
830 | |||
831 | /* compare channel by channel */ | ||
832 |
2/2✓ Branch 0 taken 716 times.
✓ Branch 1 taken 450 times.
|
1166 | for (i = 0; i < chl->nb_channels; i++) |
833 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 716 times.
|
1432 | if (av_channel_layout_channel_from_index(chl, i) != |
834 | 716 | av_channel_layout_channel_from_index(chl1, i)) | |
835 | ✗ | return 1; | |
836 | 450 | return 0; | |
837 | } | ||
838 | |||
839 | 27636 | void av_channel_layout_default(AVChannelLayout *ch_layout, int nb_channels) | |
840 | { | ||
841 | int i; | ||
842 |
2/2✓ Branch 0 taken 45956 times.
✓ Branch 1 taken 15 times.
|
45971 | for (i = 0; i < FF_ARRAY_ELEMS(channel_layout_map); i++) |
843 |
2/2✓ Branch 0 taken 27621 times.
✓ Branch 1 taken 18335 times.
|
45956 | if (nb_channels == channel_layout_map[i].layout.nb_channels) { |
844 | 27621 | *ch_layout = channel_layout_map[i].layout; | |
845 | 27621 | return; | |
846 | } | ||
847 | |||
848 | 15 | ch_layout->order = AV_CHANNEL_ORDER_UNSPEC; | |
849 | 15 | ch_layout->nb_channels = nb_channels; | |
850 | } | ||
851 | |||
852 | 41 | const AVChannelLayout *av_channel_layout_standard(void **opaque) | |
853 | { | ||
854 | 41 | uintptr_t i = (uintptr_t)*opaque; | |
855 | 41 | const AVChannelLayout *ch_layout = NULL; | |
856 | |||
857 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 1 times.
|
41 | if (i < FF_ARRAY_ELEMS(channel_layout_map)) { |
858 | 40 | ch_layout = &channel_layout_map[i].layout; | |
859 | 40 | *opaque = (void*)(i + 1); | |
860 | } | ||
861 | |||
862 | 41 | return ch_layout; | |
863 | } | ||
864 | |||
865 | 1119 | uint64_t av_channel_layout_subset(const AVChannelLayout *channel_layout, | |
866 | uint64_t mask) | ||
867 | { | ||
868 | 1119 | uint64_t ret = 0; | |
869 | int i; | ||
870 | |||
871 |
3/3✓ Branch 0 taken 749 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 368 times.
|
1119 | switch (channel_layout->order) { |
872 | 749 | case AV_CHANNEL_ORDER_NATIVE: | |
873 | case AV_CHANNEL_ORDER_AMBISONIC: | ||
874 | 749 | return channel_layout->u.mask & mask; | |
875 | 2 | case AV_CHANNEL_ORDER_CUSTOM: | |
876 |
2/2✓ Branch 0 taken 128 times.
✓ Branch 1 taken 2 times.
|
130 | for (i = 0; i < 64; i++) |
877 |
4/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 122 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 2 times.
|
128 | if (mask & (1ULL << i) && av_channel_layout_index_from_channel(channel_layout, i) >= 0) |
878 | 4 | ret |= (1ULL << i); | |
879 | 2 | break; | |
880 | } | ||
881 | |||
882 | 370 | return ret; | |
883 | } | ||
884 | |||
885 | 327 | int av_channel_layout_retype(AVChannelLayout *channel_layout, enum AVChannelOrder order, int flags) | |
886 | { | ||
887 | 327 | int allow_lossy = !(flags & AV_CHANNEL_LAYOUT_RETYPE_FLAG_LOSSLESS); | |
888 | int lossy; | ||
889 | |||
890 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 327 times.
|
327 | if (!av_channel_layout_check(channel_layout)) |
891 | ✗ | return AVERROR(EINVAL); | |
892 | |||
893 |
2/2✓ Branch 0 taken 307 times.
✓ Branch 1 taken 20 times.
|
327 | if (flags & AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL) |
894 | 307 | order = canonical_order(channel_layout); | |
895 | |||
896 |
2/2✓ Branch 0 taken 202 times.
✓ Branch 1 taken 125 times.
|
327 | if (channel_layout->order == order) |
897 | 202 | return 0; | |
898 | |||
899 |
4/5✓ Branch 0 taken 4 times.
✓ Branch 1 taken 113 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
|
125 | switch (order) { |
900 | 4 | case AV_CHANNEL_ORDER_UNSPEC: { | |
901 | 4 | int nb_channels = channel_layout->nb_channels; | |
902 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | if (channel_layout->order == AV_CHANNEL_ORDER_CUSTOM) { |
903 | 2 | lossy = 0; | |
904 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | for (int i = 0; i < nb_channels; i++) { |
905 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
2 | if (channel_layout->u.map[i].id != AV_CHAN_UNKNOWN || channel_layout->u.map[i].name[0]) { |
906 | 2 | lossy = 1; | |
907 | 2 | break; | |
908 | } | ||
909 | } | ||
910 | } else { | ||
911 | 2 | lossy = 1; | |
912 | } | ||
913 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | if (!lossy || allow_lossy) { |
914 | 4 | void *opaque = channel_layout->opaque; | |
915 | 4 | av_channel_layout_uninit(channel_layout); | |
916 | 4 | channel_layout->order = AV_CHANNEL_ORDER_UNSPEC; | |
917 | 4 | channel_layout->nb_channels = nb_channels; | |
918 | 4 | channel_layout->opaque = opaque; | |
919 | 4 | return lossy; | |
920 | } | ||
921 | ✗ | return AVERROR(ENOSYS); | |
922 | } | ||
923 | 113 | case AV_CHANNEL_ORDER_NATIVE: | |
924 |
2/2✓ Branch 0 taken 111 times.
✓ Branch 1 taken 2 times.
|
113 | if (channel_layout->order == AV_CHANNEL_ORDER_CUSTOM) { |
925 | 111 | int64_t mask = masked_description(channel_layout, 0); | |
926 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 110 times.
|
111 | if (mask < 0) |
927 | 1 | return AVERROR(ENOSYS); | |
928 | 110 | lossy = has_channel_names(channel_layout); | |
929 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 109 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
110 | if (!lossy || allow_lossy) { |
930 | 110 | void *opaque = channel_layout->opaque; | |
931 | 110 | av_channel_layout_uninit(channel_layout); | |
932 | 110 | av_channel_layout_from_mask(channel_layout, mask); | |
933 | 110 | channel_layout->opaque = opaque; | |
934 | 110 | return lossy; | |
935 | } | ||
936 | } | ||
937 | 2 | return AVERROR(ENOSYS); | |
938 | 3 | case AV_CHANNEL_ORDER_CUSTOM: { | |
939 | 3 | AVChannelLayout custom = { 0 }; | |
940 | 3 | int ret = av_channel_layout_custom_init(&custom, channel_layout->nb_channels); | |
941 | 3 | void *opaque = channel_layout->opaque; | |
942 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (ret < 0) |
943 | ✗ | return ret; | |
944 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | if (channel_layout->order != AV_CHANNEL_ORDER_UNSPEC) |
945 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 2 times.
|
15 | for (int i = 0; i < channel_layout->nb_channels; i++) |
946 | 13 | custom.u.map[i].id = av_channel_layout_channel_from_index(channel_layout, i); | |
947 | 3 | av_channel_layout_uninit(channel_layout); | |
948 | 3 | *channel_layout = custom; | |
949 | 3 | channel_layout->opaque = opaque; | |
950 | 3 | return 0; | |
951 | } | ||
952 | 5 | case AV_CHANNEL_ORDER_AMBISONIC: | |
953 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
|
5 | if (channel_layout->order == AV_CHANNEL_ORDER_CUSTOM) { |
954 | int64_t mask; | ||
955 | 3 | int nb_channels = channel_layout->nb_channels; | |
956 | 3 | int order = av_channel_layout_ambisonic_order(channel_layout); | |
957 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | if (order < 0) |
958 | 2 | return AVERROR(ENOSYS); | |
959 | 1 | mask = masked_description(channel_layout, (order + 1) * (order + 1)); | |
960 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (mask < 0) |
961 | ✗ | return AVERROR(ENOSYS); | |
962 | 1 | lossy = has_channel_names(channel_layout); | |
963 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1 | if (!lossy || allow_lossy) { |
964 | 1 | void *opaque = channel_layout->opaque; | |
965 | 1 | av_channel_layout_uninit(channel_layout); | |
966 | 1 | channel_layout->order = AV_CHANNEL_ORDER_AMBISONIC; | |
967 | 1 | channel_layout->nb_channels = nb_channels; | |
968 | 1 | channel_layout->u.mask = mask; | |
969 | 1 | channel_layout->opaque = opaque; | |
970 | 1 | return lossy; | |
971 | } | ||
972 | } | ||
973 | 2 | return AVERROR(ENOSYS); | |
974 | ✗ | default: | |
975 | ✗ | return AVERROR(EINVAL); | |
976 | } | ||
977 | } | ||
978 |