1 /* GStreamer
2 * Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
3 * Copyright (C) 2008 Sebastian Dröge <slomo@circular-chaos.org>
4 *
5 * audio-channel-mixer.c: setup of channel conversion matrices
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include <math.h>
28 #include <string.h>
29
30 #include "audio-channel-mixer.h"
31
32 #ifndef GST_DISABLE_GST_DEBUG
33 #define GST_CAT_DEFAULT ensure_debug_category()
34 static GstDebugCategory *
ensure_debug_category(void)35 ensure_debug_category (void)
36 {
37 static gsize cat_gonce = 0;
38
39 if (g_once_init_enter (&cat_gonce)) {
40 gsize cat_done;
41
42 cat_done = (gsize) _gst_debug_category_new ("audio-channel-mixer", 0,
43 "audio-channel-mixer object");
44
45 g_once_init_leave (&cat_gonce, cat_done);
46 }
47
48 return (GstDebugCategory *) cat_gonce;
49 }
50 #else
51 #define ensure_debug_category() /* NOOP */
52 #endif /* GST_DISABLE_GST_DEBUG */
53
54
55 #define PRECISION_INT 10
56
57 typedef void (*MixerFunc) (GstAudioChannelMixer * mix, const gpointer src[],
58 gpointer dst[], gint samples);
59
60 struct _GstAudioChannelMixer
61 {
62 gint in_channels;
63 gint out_channels;
64
65 /* channel conversion matrix, m[in_channels][out_channels].
66 * If identity matrix, passthrough applies. */
67 gfloat **matrix;
68
69 /* channel conversion matrix with int values, m[in_channels][out_channels].
70 * this is matrix * (2^10) as integers */
71 gint **matrix_int;
72
73 MixerFunc func;
74 };
75
76 /**
77 * gst_audio_channel_mixer_free:
78 * @mix: a #GstAudioChannelMixer
79 *
80 * Free memory allocated by @mix.
81 */
82 void
gst_audio_channel_mixer_free(GstAudioChannelMixer * mix)83 gst_audio_channel_mixer_free (GstAudioChannelMixer * mix)
84 {
85 gint i;
86
87 /* free */
88 for (i = 0; i < mix->in_channels; i++)
89 g_free (mix->matrix[i]);
90 g_free (mix->matrix);
91 mix->matrix = NULL;
92
93 for (i = 0; i < mix->in_channels; i++)
94 g_free (mix->matrix_int[i]);
95 g_free (mix->matrix_int);
96 mix->matrix_int = NULL;
97
98 g_slice_free (GstAudioChannelMixer, mix);
99 }
100
101 /*
102 * Detect and fill in identical channels. E.g.
103 * forward the left/right front channels in a
104 * 5.1 to 2.0 conversion.
105 */
106
107 static void
gst_audio_channel_mixer_fill_identical(gfloat ** matrix,gint in_channels,GstAudioChannelPosition * in_position,gint out_channels,GstAudioChannelPosition * out_position,GstAudioChannelMixerFlags flags)108 gst_audio_channel_mixer_fill_identical (gfloat ** matrix,
109 gint in_channels, GstAudioChannelPosition * in_position, gint out_channels,
110 GstAudioChannelPosition * out_position, GstAudioChannelMixerFlags flags)
111 {
112 gint ci, co;
113
114 /* Apart from the compatible channel assignments, we can also have
115 * same channel assignments. This is much simpler, we simply copy
116 * the value from source to dest! */
117 for (co = 0; co < out_channels; co++) {
118 /* find a channel in input with same position */
119 for (ci = 0; ci < in_channels; ci++) {
120 /* If the input was unpositioned, we're simply building
121 * an identity matrix */
122 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_UNPOSITIONED_IN) {
123 matrix[ci][co] = ci == co ? 1.0 : 0.0;
124 } else if (in_position[ci] == out_position[co]) {
125 matrix[ci][co] = 1.0;
126 }
127 }
128 }
129 }
130
131 /*
132 * Detect and fill in compatible channels. E.g.
133 * forward left/right front to mono (or the other
134 * way around) when going from 2.0 to 1.0.
135 */
136
137 static void
gst_audio_channel_mixer_fill_compatible(gfloat ** matrix,gint in_channels,GstAudioChannelPosition * in_position,gint out_channels,GstAudioChannelPosition * out_position)138 gst_audio_channel_mixer_fill_compatible (gfloat ** matrix, gint in_channels,
139 GstAudioChannelPosition * in_position, gint out_channels,
140 GstAudioChannelPosition * out_position)
141 {
142 /* Conversions from one-channel to compatible two-channel configs */
143 struct
144 {
145 GstAudioChannelPosition pos1[2];
146 GstAudioChannelPosition pos2[1];
147 } conv[] = {
148 /* front: mono <-> stereo */
149 { {
150 GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
151 GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT}, {
152 GST_AUDIO_CHANNEL_POSITION_MONO}},
153 /* front center: 2 <-> 1 */
154 { {
155 GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
156 GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER}, {
157 GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER}},
158 /* rear: 2 <-> 1 */
159 { {
160 GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
161 GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}, {
162 GST_AUDIO_CHANNEL_POSITION_REAR_CENTER}}, { {
163 GST_AUDIO_CHANNEL_POSITION_INVALID}}
164 };
165 gint c;
166
167 /* conversions from compatible (but not the same) channel schemes */
168 for (c = 0; conv[c].pos1[0] != GST_AUDIO_CHANNEL_POSITION_INVALID; c++) {
169 gint pos1_0 = -1, pos1_1 = -1, pos1_2 = -1;
170 gint pos2_0 = -1, pos2_1 = -1, pos2_2 = -1;
171 gint n;
172
173 for (n = 0; n < in_channels; n++) {
174 if (in_position[n] == conv[c].pos1[0])
175 pos1_0 = n;
176 else if (in_position[n] == conv[c].pos1[1])
177 pos1_1 = n;
178 else if (in_position[n] == conv[c].pos2[0])
179 pos1_2 = n;
180 }
181 for (n = 0; n < out_channels; n++) {
182 if (out_position[n] == conv[c].pos1[0])
183 pos2_0 = n;
184 else if (out_position[n] == conv[c].pos1[1])
185 pos2_1 = n;
186 else if (out_position[n] == conv[c].pos2[0])
187 pos2_2 = n;
188 }
189
190 /* The general idea here is to fill in channels from the same position
191 * as good as possible. This means mixing left<->center and right<->center.
192 */
193
194 /* left -> center */
195 if (pos1_0 != -1 && pos1_2 == -1 && pos2_0 == -1 && pos2_2 != -1)
196 matrix[pos1_0][pos2_2] = 1.0;
197 else if (pos1_0 != -1 && pos1_2 != -1 && pos2_0 == -1 && pos2_2 != -1)
198 matrix[pos1_0][pos2_2] = 0.5;
199 else if (pos1_0 != -1 && pos1_2 == -1 && pos2_0 != -1 && pos2_2 != -1)
200 matrix[pos1_0][pos2_2] = 1.0;
201
202 /* right -> center */
203 if (pos1_1 != -1 && pos1_2 == -1 && pos2_1 == -1 && pos2_2 != -1)
204 matrix[pos1_1][pos2_2] = 1.0;
205 else if (pos1_1 != -1 && pos1_2 != -1 && pos2_1 == -1 && pos2_2 != -1)
206 matrix[pos1_1][pos2_2] = 0.5;
207 else if (pos1_1 != -1 && pos1_2 == -1 && pos2_1 != -1 && pos2_2 != -1)
208 matrix[pos1_1][pos2_2] = 1.0;
209
210 /* center -> left */
211 if (pos1_2 != -1 && pos1_0 == -1 && pos2_2 == -1 && pos2_0 != -1)
212 matrix[pos1_2][pos2_0] = 1.0;
213 else if (pos1_2 != -1 && pos1_0 != -1 && pos2_2 == -1 && pos2_0 != -1)
214 matrix[pos1_2][pos2_0] = 0.5;
215 else if (pos1_2 != -1 && pos1_0 == -1 && pos2_2 != -1 && pos2_0 != -1)
216 matrix[pos1_2][pos2_0] = 1.0;
217
218 /* center -> right */
219 if (pos1_2 != -1 && pos1_1 == -1 && pos2_2 == -1 && pos2_1 != -1)
220 matrix[pos1_2][pos2_1] = 1.0;
221 else if (pos1_2 != -1 && pos1_1 != -1 && pos2_2 == -1 && pos2_1 != -1)
222 matrix[pos1_2][pos2_1] = 0.5;
223 else if (pos1_2 != -1 && pos1_1 == -1 && pos2_2 != -1 && pos2_1 != -1)
224 matrix[pos1_2][pos2_1] = 1.0;
225 }
226 }
227
228 /*
229 * Detect and fill in channels not handled by the
230 * above two, e.g. center to left/right front in
231 * 5.1 to 2.0 (or the other way around).
232 *
233 * Unfortunately, limited to static conversions
234 * for now.
235 */
236
237 static void
gst_audio_channel_mixer_detect_pos(gint channels,GstAudioChannelPosition position[64],gint * f,gboolean * has_f,gint * c,gboolean * has_c,gint * r,gboolean * has_r,gint * s,gboolean * has_s,gint * b,gboolean * has_b)238 gst_audio_channel_mixer_detect_pos (gint channels,
239 GstAudioChannelPosition position[64], gint * f, gboolean * has_f, gint * c,
240 gboolean * has_c, gint * r, gboolean * has_r, gint * s, gboolean * has_s,
241 gint * b, gboolean * has_b)
242 {
243 gint n;
244
245 for (n = 0; n < channels; n++) {
246 switch (position[n]) {
247 case GST_AUDIO_CHANNEL_POSITION_MONO:
248 f[1] = n;
249 *has_f = TRUE;
250 break;
251 case GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT:
252 f[0] = n;
253 *has_f = TRUE;
254 break;
255 case GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT:
256 f[2] = n;
257 *has_f = TRUE;
258 break;
259 case GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER:
260 c[1] = n;
261 *has_c = TRUE;
262 break;
263 case GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
264 c[0] = n;
265 *has_c = TRUE;
266 break;
267 case GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
268 c[2] = n;
269 *has_c = TRUE;
270 break;
271 case GST_AUDIO_CHANNEL_POSITION_REAR_CENTER:
272 r[1] = n;
273 *has_r = TRUE;
274 break;
275 case GST_AUDIO_CHANNEL_POSITION_REAR_LEFT:
276 r[0] = n;
277 *has_r = TRUE;
278 break;
279 case GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT:
280 r[2] = n;
281 *has_r = TRUE;
282 break;
283 case GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT:
284 s[0] = n;
285 *has_s = TRUE;
286 break;
287 case GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT:
288 s[2] = n;
289 *has_s = TRUE;
290 break;
291 case GST_AUDIO_CHANNEL_POSITION_LFE1:
292 *has_b = TRUE;
293 b[1] = n;
294 break;
295 default:
296 break;
297 }
298 }
299 }
300
301 static void
gst_audio_channel_mixer_fill_one_other(gfloat ** matrix,gint * from_idx,gint * to_idx,gfloat ratio)302 gst_audio_channel_mixer_fill_one_other (gfloat ** matrix,
303 gint * from_idx, gint * to_idx, gfloat ratio)
304 {
305
306 /* src & dst have center => passthrough */
307 if (from_idx[1] != -1 && to_idx[1] != -1) {
308 matrix[from_idx[1]][to_idx[1]] = ratio;
309 }
310
311 /* src & dst have left => passthrough */
312 if (from_idx[0] != -1 && to_idx[0] != -1) {
313 matrix[from_idx[0]][to_idx[0]] = ratio;
314 }
315
316 /* src & dst have right => passthrough */
317 if (from_idx[2] != -1 && to_idx[2] != -1) {
318 matrix[from_idx[2]][to_idx[2]] = ratio;
319 }
320
321 /* src has left & dst has center => put into center */
322 if (from_idx[0] != -1 && to_idx[1] != -1 && from_idx[1] != -1) {
323 matrix[from_idx[0]][to_idx[1]] = 0.5 * ratio;
324 } else if (from_idx[0] != -1 && to_idx[1] != -1 && from_idx[1] == -1) {
325 matrix[from_idx[0]][to_idx[1]] = ratio;
326 }
327
328 /* src has right & dst has center => put into center */
329 if (from_idx[2] != -1 && to_idx[1] != -1 && from_idx[1] != -1) {
330 matrix[from_idx[2]][to_idx[1]] = 0.5 * ratio;
331 } else if (from_idx[2] != -1 && to_idx[1] != -1 && from_idx[1] == -1) {
332 matrix[from_idx[2]][to_idx[1]] = ratio;
333 }
334
335 /* src has center & dst has left => passthrough */
336 if (from_idx[1] != -1 && to_idx[0] != -1 && from_idx[0] != -1) {
337 matrix[from_idx[1]][to_idx[0]] = 0.5 * ratio;
338 } else if (from_idx[1] != -1 && to_idx[0] != -1 && from_idx[0] == -1) {
339 matrix[from_idx[1]][to_idx[0]] = ratio;
340 }
341
342 /* src has center & dst has right => passthrough */
343 if (from_idx[1] != -1 && to_idx[2] != -1 && from_idx[2] != -1) {
344 matrix[from_idx[1]][to_idx[2]] = 0.5 * ratio;
345 } else if (from_idx[1] != -1 && to_idx[2] != -1 && from_idx[2] == -1) {
346 matrix[from_idx[1]][to_idx[2]] = ratio;
347 }
348 }
349
350 #define RATIO_CENTER_FRONT (1.0 / sqrt (2.0))
351 #define RATIO_CENTER_SIDE (1.0 / 2.0)
352 #define RATIO_CENTER_REAR (1.0 / sqrt (8.0))
353
354 #define RATIO_FRONT_CENTER (1.0 / sqrt (2.0))
355 #define RATIO_FRONT_SIDE (1.0 / sqrt (2.0))
356 #define RATIO_FRONT_REAR (1.0 / 2.0)
357
358 #define RATIO_SIDE_CENTER (1.0 / 2.0)
359 #define RATIO_SIDE_FRONT (1.0 / sqrt (2.0))
360 #define RATIO_SIDE_REAR (1.0 / sqrt (2.0))
361
362 #define RATIO_CENTER_BASS (1.0 / sqrt (2.0))
363 #define RATIO_FRONT_BASS (1.0)
364 #define RATIO_SIDE_BASS (1.0 / sqrt (2.0))
365 #define RATIO_REAR_BASS (1.0 / sqrt (2.0))
366
367 static void
gst_audio_channel_mixer_fill_others(gfloat ** matrix,gint in_channels,GstAudioChannelPosition * in_position,gint out_channels,GstAudioChannelPosition * out_position)368 gst_audio_channel_mixer_fill_others (gfloat ** matrix, gint in_channels,
369 GstAudioChannelPosition * in_position, gint out_channels,
370 GstAudioChannelPosition * out_position)
371 {
372 gboolean in_has_front = FALSE, out_has_front = FALSE,
373 in_has_center = FALSE, out_has_center = FALSE,
374 in_has_rear = FALSE, out_has_rear = FALSE,
375 in_has_side = FALSE, out_has_side = FALSE,
376 in_has_bass = FALSE, out_has_bass = FALSE;
377 /* LEFT, RIGHT, MONO */
378 gint in_f[3] = { -1, -1, -1 };
379 gint out_f[3] = { -1, -1, -1 };
380 /* LOC, ROC, CENTER */
381 gint in_c[3] = { -1, -1, -1 };
382 gint out_c[3] = { -1, -1, -1 };
383 /* RLEFT, RRIGHT, RCENTER */
384 gint in_r[3] = { -1, -1, -1 };
385 gint out_r[3] = { -1, -1, -1 };
386 /* SLEFT, INVALID, SRIGHT */
387 gint in_s[3] = { -1, -1, -1 };
388 gint out_s[3] = { -1, -1, -1 };
389 /* INVALID, LFE, INVALID */
390 gint in_b[3] = { -1, -1, -1 };
391 gint out_b[3] = { -1, -1, -1 };
392
393 /* First see where (if at all) the various channels from/to
394 * which we want to convert are located in our matrix/array. */
395 gst_audio_channel_mixer_detect_pos (in_channels, in_position,
396 in_f, &in_has_front,
397 in_c, &in_has_center, in_r, &in_has_rear,
398 in_s, &in_has_side, in_b, &in_has_bass);
399 gst_audio_channel_mixer_detect_pos (out_channels, out_position,
400 out_f, &out_has_front,
401 out_c, &out_has_center, out_r, &out_has_rear,
402 out_s, &out_has_side, out_b, &out_has_bass);
403
404 /* The general idea here is:
405 * - if the source has a channel that the destination doesn't have mix
406 * it into the nearest available destination channel
407 * - if the destination has a channel that the source doesn't have mix
408 * the nearest source channel into the destination channel
409 *
410 * The ratio for the mixing becomes lower as the distance between the
411 * channels gets larger
412 */
413
414 /* center <-> front/side/rear */
415 if (!in_has_center && in_has_front && out_has_center) {
416 gst_audio_channel_mixer_fill_one_other (matrix, in_f, out_c,
417 RATIO_CENTER_FRONT);
418 } else if (!in_has_center && !in_has_front && in_has_side && out_has_center) {
419 gst_audio_channel_mixer_fill_one_other (matrix, in_s, out_c,
420 RATIO_CENTER_SIDE);
421 } else if (!in_has_center && !in_has_front && !in_has_side && in_has_rear
422 && out_has_center) {
423 gst_audio_channel_mixer_fill_one_other (matrix, in_r, out_c,
424 RATIO_CENTER_REAR);
425 } else if (in_has_center && !out_has_center && out_has_front) {
426 gst_audio_channel_mixer_fill_one_other (matrix, in_c, out_f,
427 RATIO_CENTER_FRONT);
428 } else if (in_has_center && !out_has_center && !out_has_front && out_has_side) {
429 gst_audio_channel_mixer_fill_one_other (matrix, in_c, out_s,
430 RATIO_CENTER_SIDE);
431 } else if (in_has_center && !out_has_center && !out_has_front && !out_has_side
432 && out_has_rear) {
433 gst_audio_channel_mixer_fill_one_other (matrix, in_c, out_r,
434 RATIO_CENTER_REAR);
435 }
436
437 /* front <-> center/side/rear */
438 if (!in_has_front && in_has_center && !in_has_side && out_has_front) {
439 gst_audio_channel_mixer_fill_one_other (matrix, in_c, out_f,
440 RATIO_CENTER_FRONT);
441 } else if (!in_has_front && !in_has_center && in_has_side && out_has_front) {
442 gst_audio_channel_mixer_fill_one_other (matrix, in_s, out_f,
443 RATIO_FRONT_SIDE);
444 } else if (!in_has_front && in_has_center && in_has_side && out_has_front) {
445 gst_audio_channel_mixer_fill_one_other (matrix, in_c, out_f,
446 0.5 * RATIO_CENTER_FRONT);
447 gst_audio_channel_mixer_fill_one_other (matrix, in_s, out_f,
448 0.5 * RATIO_FRONT_SIDE);
449 } else if (!in_has_front && !in_has_center && !in_has_side && in_has_rear
450 && out_has_front) {
451 gst_audio_channel_mixer_fill_one_other (matrix, in_r, out_f,
452 RATIO_FRONT_REAR);
453 } else if (in_has_front && out_has_center && !out_has_side && !out_has_front) {
454 gst_audio_channel_mixer_fill_one_other (matrix,
455 in_f, out_c, RATIO_CENTER_FRONT);
456 } else if (in_has_front && !out_has_center && out_has_side && !out_has_front) {
457 gst_audio_channel_mixer_fill_one_other (matrix, in_f, out_s,
458 RATIO_FRONT_SIDE);
459 } else if (in_has_front && out_has_center && out_has_side && !out_has_front) {
460 gst_audio_channel_mixer_fill_one_other (matrix, in_f, out_c,
461 0.5 * RATIO_CENTER_FRONT);
462 gst_audio_channel_mixer_fill_one_other (matrix, in_f, out_s,
463 0.5 * RATIO_FRONT_SIDE);
464 } else if (in_has_front && !out_has_center && !out_has_side && !out_has_front
465 && out_has_rear) {
466 gst_audio_channel_mixer_fill_one_other (matrix, in_f, out_r,
467 RATIO_FRONT_REAR);
468 }
469
470 /* side <-> center/front/rear */
471 if (!in_has_side && in_has_front && !in_has_rear && out_has_side) {
472 gst_audio_channel_mixer_fill_one_other (matrix, in_f, out_s,
473 RATIO_FRONT_SIDE);
474 } else if (!in_has_side && !in_has_front && in_has_rear && out_has_side) {
475 gst_audio_channel_mixer_fill_one_other (matrix, in_r, out_s,
476 RATIO_SIDE_REAR);
477 } else if (!in_has_side && in_has_front && in_has_rear && out_has_side) {
478 gst_audio_channel_mixer_fill_one_other (matrix, in_f, out_s,
479 0.5 * RATIO_FRONT_SIDE);
480 gst_audio_channel_mixer_fill_one_other (matrix, in_r, out_s,
481 0.5 * RATIO_SIDE_REAR);
482 } else if (!in_has_side && !in_has_front && !in_has_rear && in_has_center
483 && out_has_side) {
484 gst_audio_channel_mixer_fill_one_other (matrix, in_c, out_s,
485 RATIO_CENTER_SIDE);
486 } else if (in_has_side && out_has_front && !out_has_rear && !out_has_side) {
487 gst_audio_channel_mixer_fill_one_other (matrix, in_s, out_f,
488 RATIO_FRONT_SIDE);
489 } else if (in_has_side && !out_has_front && out_has_rear && !out_has_side) {
490 gst_audio_channel_mixer_fill_one_other (matrix, in_s, out_r,
491 RATIO_SIDE_REAR);
492 } else if (in_has_side && out_has_front && out_has_rear && !out_has_side) {
493 gst_audio_channel_mixer_fill_one_other (matrix, in_s, out_f,
494 0.5 * RATIO_FRONT_SIDE);
495 gst_audio_channel_mixer_fill_one_other (matrix, in_s, out_r,
496 0.5 * RATIO_SIDE_REAR);
497 } else if (in_has_side && !out_has_front && !out_has_rear && out_has_center
498 && !out_has_side) {
499 gst_audio_channel_mixer_fill_one_other (matrix, in_s, out_c,
500 RATIO_CENTER_SIDE);
501 }
502
503 /* rear <-> center/front/side */
504 if (!in_has_rear && in_has_side && out_has_rear) {
505 gst_audio_channel_mixer_fill_one_other (matrix, in_s, out_r,
506 RATIO_SIDE_REAR);
507 } else if (!in_has_rear && !in_has_side && in_has_front && out_has_rear) {
508 gst_audio_channel_mixer_fill_one_other (matrix, in_f, out_r,
509 RATIO_FRONT_REAR);
510 } else if (!in_has_rear && !in_has_side && !in_has_front && in_has_center
511 && out_has_rear) {
512 gst_audio_channel_mixer_fill_one_other (matrix, in_c, out_r,
513 RATIO_CENTER_REAR);
514 } else if (in_has_rear && !out_has_rear && out_has_side) {
515 gst_audio_channel_mixer_fill_one_other (matrix, in_r, out_s,
516 RATIO_SIDE_REAR);
517 } else if (in_has_rear && !out_has_rear && !out_has_side && out_has_front) {
518 gst_audio_channel_mixer_fill_one_other (matrix, in_r, out_f,
519 RATIO_FRONT_REAR);
520 } else if (in_has_rear && !out_has_rear && !out_has_side && !out_has_front
521 && out_has_center) {
522 gst_audio_channel_mixer_fill_one_other (matrix, in_r, out_c,
523 RATIO_CENTER_REAR);
524 }
525
526 /* bass <-> any */
527 if (in_has_bass && !out_has_bass) {
528 if (out_has_center) {
529 gst_audio_channel_mixer_fill_one_other (matrix, in_b, out_c,
530 RATIO_CENTER_BASS);
531 }
532 if (out_has_front) {
533 gst_audio_channel_mixer_fill_one_other (matrix, in_b, out_f,
534 RATIO_FRONT_BASS);
535 }
536 if (out_has_side) {
537 gst_audio_channel_mixer_fill_one_other (matrix, in_b, out_s,
538 RATIO_SIDE_BASS);
539 }
540 if (out_has_rear) {
541 gst_audio_channel_mixer_fill_one_other (matrix, in_b, out_r,
542 RATIO_REAR_BASS);
543 }
544 } else if (!in_has_bass && out_has_bass) {
545 if (in_has_center) {
546 gst_audio_channel_mixer_fill_one_other (matrix, in_c, out_b,
547 RATIO_CENTER_BASS);
548 }
549 if (in_has_front) {
550 gst_audio_channel_mixer_fill_one_other (matrix, in_f, out_b,
551 RATIO_FRONT_BASS);
552 }
553 if (in_has_side) {
554 gst_audio_channel_mixer_fill_one_other (matrix, in_s, out_b,
555 RATIO_REAR_BASS);
556 }
557 if (in_has_rear) {
558 gst_audio_channel_mixer_fill_one_other (matrix, in_r, out_b,
559 RATIO_REAR_BASS);
560 }
561 }
562 }
563
564 /*
565 * Normalize output values.
566 */
567
568 static void
gst_audio_channel_mixer_fill_normalize(gfloat ** matrix,gint in_channels,gint out_channels)569 gst_audio_channel_mixer_fill_normalize (gfloat ** matrix, gint in_channels,
570 gint out_channels)
571 {
572 gfloat sum, top = 0;
573 gint i, j;
574
575 for (j = 0; j < out_channels; j++) {
576 /* calculate sum */
577 sum = 0.0;
578 for (i = 0; i < in_channels; i++) {
579 sum += fabs (matrix[i][j]);
580 }
581 if (sum > top) {
582 top = sum;
583 }
584 }
585
586 /* normalize to mix */
587 if (top == 0.0)
588 return;
589
590 for (j = 0; j < out_channels; j++) {
591 for (i = 0; i < in_channels; i++) {
592 matrix[i][j] /= top;
593 }
594 }
595 }
596
597 static gboolean
gst_audio_channel_mixer_fill_special(gfloat ** matrix,gint in_channels,GstAudioChannelPosition * in_position,gint out_channels,GstAudioChannelPosition * out_position)598 gst_audio_channel_mixer_fill_special (gfloat ** matrix, gint in_channels,
599 GstAudioChannelPosition * in_position, gint out_channels,
600 GstAudioChannelPosition * out_position)
601 {
602 /* Special, standard conversions here */
603
604 /* Mono<->Stereo, just a fast-path */
605 if (in_channels == 2 && out_channels == 1 &&
606 ((in_position[0] == GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT &&
607 in_position[1] == GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT) ||
608 (in_position[0] == GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT &&
609 in_position[1] == GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT)) &&
610 out_position[0] == GST_AUDIO_CHANNEL_POSITION_MONO) {
611 matrix[0][0] = 0.5;
612 matrix[1][0] = 0.5;
613 return TRUE;
614 } else if (in_channels == 1 && out_channels == 2 &&
615 ((out_position[0] == GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT &&
616 out_position[1] == GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT) ||
617 (out_position[0] == GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT &&
618 out_position[1] == GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT)) &&
619 in_position[0] == GST_AUDIO_CHANNEL_POSITION_MONO) {
620 matrix[0][0] = 1.0;
621 matrix[0][1] = 1.0;
622 return TRUE;
623 }
624
625 /* TODO: 5.1 <-> Stereo and other standard conversions */
626
627 return FALSE;
628 }
629
630 /*
631 * Automagically generate conversion matrix.
632 */
633
634 static void
gst_audio_channel_mixer_fill_matrix(gfloat ** matrix,GstAudioChannelMixerFlags flags,gint in_channels,GstAudioChannelPosition * in_position,gint out_channels,GstAudioChannelPosition * out_position)635 gst_audio_channel_mixer_fill_matrix (gfloat ** matrix,
636 GstAudioChannelMixerFlags flags, gint in_channels,
637 GstAudioChannelPosition * in_position, gint out_channels,
638 GstAudioChannelPosition * out_position)
639 {
640 if (gst_audio_channel_mixer_fill_special (matrix, in_channels, in_position,
641 out_channels, out_position))
642 return;
643
644 gst_audio_channel_mixer_fill_identical (matrix, in_channels, in_position,
645 out_channels, out_position, flags);
646
647 if (!(flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_UNPOSITIONED_IN)) {
648 gst_audio_channel_mixer_fill_compatible (matrix, in_channels, in_position,
649 out_channels, out_position);
650 gst_audio_channel_mixer_fill_others (matrix, in_channels, in_position,
651 out_channels, out_position);
652 gst_audio_channel_mixer_fill_normalize (matrix, in_channels, out_channels);
653 }
654 }
655
656 /* only call mix after mix->matrix is fully set up and normalized */
657 static void
gst_audio_channel_mixer_setup_matrix_int(GstAudioChannelMixer * mix)658 gst_audio_channel_mixer_setup_matrix_int (GstAudioChannelMixer * mix)
659 {
660 gint i, j;
661 gfloat tmp;
662 gfloat factor = (1 << PRECISION_INT);
663
664 mix->matrix_int = g_new0 (gint *, mix->in_channels);
665
666 for (i = 0; i < mix->in_channels; i++) {
667 mix->matrix_int[i] = g_new (gint, mix->out_channels);
668
669 for (j = 0; j < mix->out_channels; j++) {
670 tmp = mix->matrix[i][j] * factor;
671 mix->matrix_int[i][j] = (gint) tmp;
672 }
673 }
674 }
675
676 static gfloat **
gst_audio_channel_mixer_setup_matrix(GstAudioChannelMixerFlags flags,gint in_channels,GstAudioChannelPosition * in_position,gint out_channels,GstAudioChannelPosition * out_position)677 gst_audio_channel_mixer_setup_matrix (GstAudioChannelMixerFlags flags,
678 gint in_channels, GstAudioChannelPosition * in_position,
679 gint out_channels, GstAudioChannelPosition * out_position)
680 {
681 gint i, j;
682 gfloat **matrix = g_new0 (gfloat *, in_channels);
683
684 for (i = 0; i < in_channels; i++) {
685 matrix[i] = g_new (gfloat, out_channels);
686 for (j = 0; j < out_channels; j++)
687 matrix[i][j] = 0.;
688 }
689
690 /* setup the matrix' internal values */
691 gst_audio_channel_mixer_fill_matrix (matrix, flags, in_channels, in_position,
692 out_channels, out_position);
693
694 return matrix;
695 }
696
697 #define DEFINE_GET_DATA_FUNCS(type) \
698 static inline type \
699 _get_in_data_interleaved_##type (const type * in_data[], \
700 gint sample, gint channel, gint total_channels) \
701 { \
702 return in_data[0][sample * total_channels + channel]; \
703 } \
704 \
705 static inline type * \
706 _get_out_data_interleaved_##type (type * out_data[], \
707 gint sample, gint channel, gint total_channels) \
708 { \
709 return &out_data[0][sample * total_channels + channel]; \
710 } \
711 \
712 static inline type \
713 _get_in_data_planar_##type (const type * in_data[], \
714 gint sample, gint channel, gint total_channels) \
715 { \
716 (void) total_channels; \
717 return in_data[channel][sample]; \
718 } \
719 \
720 static inline type * \
721 _get_out_data_planar_##type (type * out_data[], \
722 gint sample, gint channel, gint total_channels) \
723 { \
724 (void) total_channels; \
725 return &out_data[channel][sample]; \
726 }
727
728 #define DEFINE_INTEGER_MIX_FUNC(bits, resbits, inlayout, outlayout) \
729 static void \
730 gst_audio_channel_mixer_mix_int##bits##_##inlayout##_##outlayout ( \
731 GstAudioChannelMixer * mix, const gint##bits * in_data[], \
732 gint##bits * out_data[], gint samples) \
733 { \
734 gint in, out, n; \
735 gint##resbits res; \
736 gint inchannels, outchannels; \
737 \
738 inchannels = mix->in_channels; \
739 outchannels = mix->out_channels; \
740 \
741 for (n = 0; n < samples; n++) { \
742 for (out = 0; out < outchannels; out++) { \
743 /* convert */ \
744 res = 0; \
745 for (in = 0; in < inchannels; in++) \
746 res += \
747 _get_in_data_##inlayout##_gint##bits (in_data, n, in, inchannels) * \
748 (gint##resbits) mix->matrix_int[in][out]; \
749 \
750 /* remove factor from int matrix */ \
751 res = (res + (1 << (PRECISION_INT - 1))) >> PRECISION_INT; \
752 *_get_out_data_##outlayout##_gint##bits (out_data, n, out, outchannels) = \
753 CLAMP (res, G_MININT##bits, G_MAXINT##bits); \
754 } \
755 } \
756 }
757
758 #define DEFINE_FLOAT_MIX_FUNC(type, inlayout, outlayout) \
759 static void \
760 gst_audio_channel_mixer_mix_##type##_##inlayout##_##outlayout ( \
761 GstAudioChannelMixer * mix, const g##type * in_data[], \
762 g##type * out_data[], gint samples) \
763 { \
764 gint in, out, n; \
765 g##type res; \
766 gint inchannels, outchannels; \
767 \
768 inchannels = mix->in_channels; \
769 outchannels = mix->out_channels; \
770 \
771 for (n = 0; n < samples; n++) { \
772 for (out = 0; out < outchannels; out++) { \
773 /* convert */ \
774 res = 0.0; \
775 for (in = 0; in < inchannels; in++) \
776 res += \
777 _get_in_data_##inlayout##_g##type (in_data, n, in, inchannels) * \
778 mix->matrix[in][out]; \
779 \
780 *_get_out_data_##outlayout##_g##type (out_data, n, out, outchannels) = res; \
781 } \
782 } \
783 }
784
785 DEFINE_GET_DATA_FUNCS (gint16);
786 DEFINE_INTEGER_MIX_FUNC (16, 32, interleaved, interleaved);
787 DEFINE_INTEGER_MIX_FUNC (16, 32, interleaved, planar);
788 DEFINE_INTEGER_MIX_FUNC (16, 32, planar, interleaved);
789 DEFINE_INTEGER_MIX_FUNC (16, 32, planar, planar);
790
791 DEFINE_GET_DATA_FUNCS (gint32);
792 DEFINE_INTEGER_MIX_FUNC (32, 64, interleaved, interleaved);
793 DEFINE_INTEGER_MIX_FUNC (32, 64, interleaved, planar);
794 DEFINE_INTEGER_MIX_FUNC (32, 64, planar, interleaved);
795 DEFINE_INTEGER_MIX_FUNC (32, 64, planar, planar);
796
797 DEFINE_GET_DATA_FUNCS (gfloat);
798 DEFINE_FLOAT_MIX_FUNC (float, interleaved, interleaved);
799 DEFINE_FLOAT_MIX_FUNC (float, interleaved, planar);
800 DEFINE_FLOAT_MIX_FUNC (float, planar, interleaved);
801 DEFINE_FLOAT_MIX_FUNC (float, planar, planar);
802
803 DEFINE_GET_DATA_FUNCS (gdouble);
804 DEFINE_FLOAT_MIX_FUNC (double, interleaved, interleaved);
805 DEFINE_FLOAT_MIX_FUNC (double, interleaved, planar);
806 DEFINE_FLOAT_MIX_FUNC (double, planar, interleaved);
807 DEFINE_FLOAT_MIX_FUNC (double, planar, planar);
808
809 /**
810 * gst_audio_channel_mixer_new_with_matrix: (skip):
811 * @flags: #GstAudioChannelMixerFlags
812 * @in_channels: number of input channels
813 * @out_channels: number of output channels
814 * @matrix: (transfer full) (nullable): channel conversion matrix, m[@in_channels][@out_channels].
815 * If identity matrix, passthrough applies. If %NULL, a (potentially truncated)
816 * identity matrix is generated.
817 *
818 * Create a new channel mixer object for the given parameters.
819 *
820 * Returns: a new #GstAudioChannelMixer object, or %NULL if @format isn't supported,
821 * @matrix is invalid, or @matrix is %NULL and @in_channels != @out_channels.
822 * Free with gst_audio_channel_mixer_free() after usage.
823 *
824 * Since: 1.14
825 */
826 GstAudioChannelMixer *
gst_audio_channel_mixer_new_with_matrix(GstAudioChannelMixerFlags flags,GstAudioFormat format,gint in_channels,gint out_channels,gfloat ** matrix)827 gst_audio_channel_mixer_new_with_matrix (GstAudioChannelMixerFlags flags,
828 GstAudioFormat format,
829 gint in_channels, gint out_channels, gfloat ** matrix)
830 {
831 GstAudioChannelMixer *mix;
832
833 g_return_val_if_fail (format == GST_AUDIO_FORMAT_S16
834 || format == GST_AUDIO_FORMAT_S32
835 || format == GST_AUDIO_FORMAT_F32
836 || format == GST_AUDIO_FORMAT_F64, NULL);
837 g_return_val_if_fail (in_channels > 0 && in_channels < 64, NULL);
838 g_return_val_if_fail (out_channels > 0 && out_channels < 64, NULL);
839
840 mix = g_slice_new0 (GstAudioChannelMixer);
841 mix->in_channels = in_channels;
842 mix->out_channels = out_channels;
843
844 if (!matrix) {
845 /* Generate (potentially truncated) identity matrix */
846 gint i, j;
847
848 mix->matrix = g_new0 (gfloat *, in_channels);
849
850 for (i = 0; i < in_channels; i++) {
851 mix->matrix[i] = g_new (gfloat, out_channels);
852 for (j = 0; j < out_channels; j++) {
853 mix->matrix[i][j] = i == j ? 1.0 : 0.0;
854 }
855 }
856 } else {
857 mix->matrix = matrix;
858 }
859
860 gst_audio_channel_mixer_setup_matrix_int (mix);
861
862 #ifndef GST_DISABLE_GST_DEBUG
863 /* debug */
864 {
865 GString *s;
866 gint i, j;
867
868 s = g_string_new ("Matrix for");
869 g_string_append_printf (s, " %d -> %d: ",
870 mix->in_channels, mix->out_channels);
871 g_string_append (s, "{");
872 for (i = 0; i < mix->in_channels; i++) {
873 if (i != 0)
874 g_string_append (s, ",");
875 g_string_append (s, " {");
876 for (j = 0; j < mix->out_channels; j++) {
877 if (j != 0)
878 g_string_append (s, ",");
879 g_string_append_printf (s, " %f", mix->matrix[i][j]);
880 }
881 g_string_append (s, " }");
882 }
883 g_string_append (s, " }");
884 GST_DEBUG ("%s", s->str);
885 g_string_free (s, TRUE);
886 }
887 #endif
888
889 switch (format) {
890 case GST_AUDIO_FORMAT_S16:
891 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_NON_INTERLEAVED_IN) {
892 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_NON_INTERLEAVED_OUT) {
893 mix->func = (MixerFunc)
894 gst_audio_channel_mixer_mix_int16_planar_planar;
895 } else {
896 mix->func = (MixerFunc)
897 gst_audio_channel_mixer_mix_int16_planar_interleaved;
898 }
899 } else {
900 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_NON_INTERLEAVED_OUT) {
901 mix->func = (MixerFunc)
902 gst_audio_channel_mixer_mix_int16_interleaved_planar;
903 } else {
904 mix->func = (MixerFunc)
905 gst_audio_channel_mixer_mix_int16_interleaved_interleaved;
906 }
907 }
908 break;
909 case GST_AUDIO_FORMAT_S32:
910 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_NON_INTERLEAVED_IN) {
911 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_NON_INTERLEAVED_OUT) {
912 mix->func = (MixerFunc)
913 gst_audio_channel_mixer_mix_int32_planar_planar;
914 } else {
915 mix->func = (MixerFunc)
916 gst_audio_channel_mixer_mix_int32_planar_interleaved;
917 }
918 } else {
919 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_NON_INTERLEAVED_OUT) {
920 mix->func = (MixerFunc)
921 gst_audio_channel_mixer_mix_int32_interleaved_planar;
922 } else {
923 mix->func = (MixerFunc)
924 gst_audio_channel_mixer_mix_int32_interleaved_interleaved;
925 }
926 }
927 break;
928 case GST_AUDIO_FORMAT_F32:
929 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_NON_INTERLEAVED_IN) {
930 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_NON_INTERLEAVED_OUT) {
931 mix->func = (MixerFunc)
932 gst_audio_channel_mixer_mix_float_planar_planar;
933 } else {
934 mix->func = (MixerFunc)
935 gst_audio_channel_mixer_mix_float_planar_interleaved;
936 }
937 } else {
938 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_NON_INTERLEAVED_OUT) {
939 mix->func = (MixerFunc)
940 gst_audio_channel_mixer_mix_float_interleaved_planar;
941 } else {
942 mix->func = (MixerFunc)
943 gst_audio_channel_mixer_mix_float_interleaved_interleaved;
944 }
945 }
946 break;
947 case GST_AUDIO_FORMAT_F64:
948 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_NON_INTERLEAVED_IN) {
949 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_NON_INTERLEAVED_OUT) {
950 mix->func = (MixerFunc)
951 gst_audio_channel_mixer_mix_double_planar_planar;
952 } else {
953 mix->func = (MixerFunc)
954 gst_audio_channel_mixer_mix_double_planar_interleaved;
955 }
956 } else {
957 if (flags & GST_AUDIO_CHANNEL_MIXER_FLAGS_NON_INTERLEAVED_OUT) {
958 mix->func = (MixerFunc)
959 gst_audio_channel_mixer_mix_double_interleaved_planar;
960 } else {
961 mix->func = (MixerFunc)
962 gst_audio_channel_mixer_mix_double_interleaved_interleaved;
963 }
964 }
965 break;
966 default:
967 g_assert_not_reached ();
968 break;
969 }
970 return mix;
971 }
972
973 /**
974 * gst_audio_channel_mixer_new: (skip):
975 * @flags: #GstAudioChannelMixerFlags
976 * @in_channels: number of input channels
977 * @in_position: positions of input channels
978 * @out_channels: number of output channels
979 * @out_position: positions of output channels
980 *
981 * Create a new channel mixer object for the given parameters.
982 *
983 * Returns: a new #GstAudioChannelMixer object, or %NULL if @format isn't supported.
984 * Free with gst_audio_channel_mixer_free() after usage.
985 */
986 GstAudioChannelMixer *
gst_audio_channel_mixer_new(GstAudioChannelMixerFlags flags,GstAudioFormat format,gint in_channels,GstAudioChannelPosition * in_position,gint out_channels,GstAudioChannelPosition * out_position)987 gst_audio_channel_mixer_new (GstAudioChannelMixerFlags flags,
988 GstAudioFormat format,
989 gint in_channels,
990 GstAudioChannelPosition * in_position,
991 gint out_channels, GstAudioChannelPosition * out_position)
992 {
993 gfloat **matrix;
994
995 g_return_val_if_fail (format == GST_AUDIO_FORMAT_S16
996 || format == GST_AUDIO_FORMAT_S32
997 || format == GST_AUDIO_FORMAT_F32
998 || format == GST_AUDIO_FORMAT_F64, NULL);
999 g_return_val_if_fail (in_channels > 0 && in_channels < 64, NULL);
1000 g_return_val_if_fail (out_channels > 0 && out_channels < 64, NULL);
1001
1002 matrix =
1003 gst_audio_channel_mixer_setup_matrix (flags, in_channels, in_position,
1004 out_channels, out_position);
1005 return gst_audio_channel_mixer_new_with_matrix (flags, format, in_channels,
1006 out_channels, matrix);
1007 }
1008
1009 /**
1010 * gst_audio_channel_mixer_is_passthrough:
1011 * @mix: a #GstAudioChannelMixer
1012 *
1013 * Check if @mix is in passthrough.
1014 *
1015 * Only N x N mix identity matrices are considered passthrough,
1016 * this is determined by comparing the contents of the matrix
1017 * with 0.0 and 1.0.
1018 *
1019 * As this is floating point comparisons, if the values have been
1020 * generated, they should be rounded up or down by explicit
1021 * assignment of 0.0 or 1.0 to values within a user-defined
1022 * epsilon, this code doesn't make assumptions as to what may
1023 * constitute an appropriate epsilon.
1024 *
1025 * Returns: %TRUE is @mix is passthrough.
1026 */
1027 gboolean
gst_audio_channel_mixer_is_passthrough(GstAudioChannelMixer * mix)1028 gst_audio_channel_mixer_is_passthrough (GstAudioChannelMixer * mix)
1029 {
1030 gint i, j;
1031 gboolean res;
1032
1033 /* only NxN matrices can be identities */
1034 if (mix->in_channels != mix->out_channels)
1035 return FALSE;
1036
1037 res = TRUE;
1038
1039 for (i = 0; i < mix->in_channels; i++) {
1040 for (j = 0; j < mix->out_channels; j++) {
1041 if ((i == j && mix->matrix[i][j] != 1.0f) ||
1042 (i != j && mix->matrix[i][j] != 0.0f)) {
1043 res = FALSE;
1044 break;
1045 }
1046 }
1047 }
1048
1049 return res;
1050 }
1051
1052 /**
1053 * gst_audio_channel_mixer_samples:
1054 * @mix: a #GstAudioChannelMixer
1055 * @in: input samples
1056 * @out: output samples
1057 * @samples: number of samples
1058 *
1059 * In case the samples are interleaved, @in and @out must point to an
1060 * array with a single element pointing to a block of interleaved samples.
1061 *
1062 * If non-interleaved samples are used, @in and @out must point to an
1063 * array with pointers to memory blocks, one for each channel.
1064 *
1065 * Perform channel mixing on @in_data and write the result to @out_data.
1066 * @in_data and @out_data need to be in @format and @layout.
1067 */
1068 void
gst_audio_channel_mixer_samples(GstAudioChannelMixer * mix,const gpointer in[],gpointer out[],gint samples)1069 gst_audio_channel_mixer_samples (GstAudioChannelMixer * mix,
1070 const gpointer in[], gpointer out[], gint samples)
1071 {
1072 g_return_if_fail (mix != NULL);
1073 g_return_if_fail (mix->matrix != NULL);
1074
1075 mix->func (mix, in, out, samples);
1076 }
1077