/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see . */ #include #include #include #include #include #include #include #include #include "audio/aframe.h" #include "audio/chmap.h" #include "audio/format.h" #include "common/av_common.h" #include "common/global.h" #include "filters/f_swresample.h" #include "filters/filter.h" #include "filters/frame.h" #include "video/hwdec.h" #include "test_utils.h" int64_t mp_pts_to_av(double mp_pts, AVRational *tb) { abort(); } double mp_pts_from_av(int64_t av_pts, AVRational *tb) { abort(); } int mp_set_avopts(struct mp_log *log, void *avobj, char **kv) { for (int n = 0; kv && kv[n * 2]; n++) { assert_true(av_opt_set(avobj, kv[n * 2], kv[n * 2 + 1], AV_OPT_SEARCH_CHILDREN) >= 0); } return 0; } void hwdec_devices_request_for_img_fmt(struct mp_hwdec_devices *devs, struct hwdec_imgfmt_request *params) { abort(); } struct mp_hwdec_ctx *hwdec_devices_get_by_imgfmt_and_type( struct mp_hwdec_devices *devs, int hw_imgfmt, enum AVHWDeviceType device_type) { abort(); } // Input channel c carries VAL(c + 1), so routing, mixing and silence are all // visible in the output values. The values are small enough for a mix of all // channels of the largest layouts to stay below full scale. #define VAL(n) ((n) / 512.0) // All channels are scaled by this. It differs from sample to sample, so // samples that move in time are visible too. It has more bits than a float // holds, and like the values it is a multiple of a power of two. Their // product is exact then, also for code that computes with more precision // than a double has, as x87 code does. static double weight(int n) { return 0.5 + (n * 2654435761u >> 2) / 4294967296.0; } struct spec { struct mp_chmap map; int format; int rate; }; struct input { struct spec spec; int samples; bool reset; // reset the filter before this frame double speed; // set the playback speed before this frame, if not 0 }; // How channels are mixed. The filter gets the options, the reference the // values that they stand for. struct mix { bool normalize; // --audio-normalize-downmix char **avopts; // --audio-swresample-o double clev, slev, lfe; // levels of center, surround and LFE double maxval; // limit for the coefficients of a channel double volume; enum AVMatrixEncoding encoding; double gain; // level of a channel that changes its place }; #define AVOPTS(...) (char *[]){__VA_ARGS__, NULL} // What mpv sets without an option, and for --audio-normalize-downmix. The // levels are the defaults of libswresample. static const struct mix mix_default = { .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = 1000, .volume = 1, .gain = 1, }; static const struct mix mix_normalize = { .normalize = true, .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = 1, .volume = 1, .gain = 1, }; // Levels of its own for every kind of channel, and a volume. static const struct mix mix_levels = { .avopts = AVOPTS("clev", "0.5", "slev", "0.25", "lfe_mix_level", "0.75", "rmvol", "0.5"), .clev = 0.5, .slev = 0.25, .lfe = 0.75, .maxval = 1000, .volume = 0.5, .gain = 0.5, }; // Surround channels in both output channels with opposite signs. static const struct mix mix_dolby = { .normalize = true, .avopts = AVOPTS("matrix_encoding", "dolby", "rmvol", "2"), .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = 1, .volume = 2, .encoding = AV_MATRIX_ENCODING_DOLBY, .gain = 2, }; static const struct mix mix_half = { .avopts = AVOPTS("rmvol", "0.5"), .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = 1000, .volume = 0.5, .gain = 0.5, }; // A negative volume takes the place of the sum of the coefficients, which // the limit is for. static const struct mix mix_negative = { .normalize = true, .avopts = AVOPTS("rmvol", "-2"), .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = 1, .volume = -2, .gain = 0.5, }; // A volume is set, and channels that are copied keep their level. static const struct mix mix_unity = { .normalize = true, .avopts = AVOPTS("rmvol", "-1"), .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = 1, .volume = -1, .gain = 1, }; // Without a limit, libswresample has none for floating point and 1 for // integers, be it in the output or in the format that it is told to mix in. static const struct mix mix_auto_float = { .avopts = AVOPTS("rematrix_maxval", "0"), .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = INT_MAX, .volume = 1, .gain = 1, }; static const struct mix mix_auto_integer = { .avopts = AVOPTS("rematrix_maxval", "0"), .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = 1, .volume = 1, .gain = 1, }; static const struct mix mix_auto_s16p = { .avopts = AVOPTS("rematrix_maxval", "0", "internal_sample_fmt", "s16p"), .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = 1, .volume = 1, .gain = 1, }; static const struct mix mix_auto_fltp_float = { .avopts = AVOPTS("rematrix_maxval", "0", "internal_sample_fmt", "fltp"), .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = INT_MAX, .volume = 1, .gain = 1, }; static const struct mix mix_auto_fltp_integer = { .avopts = AVOPTS("rematrix_maxval", "0", "internal_sample_fmt", "fltp"), .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = 1, .volume = 1, .gain = 1, }; // A limit below 1 lowers a channel that only changes its place too. static const struct mix mix_limit = { .avopts = AVOPTS("rematrix_maxval", "0.5"), .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = 0.5, .volume = 1, .gain = 0.5, }; static const struct mix mix_limit_half = { .avopts = AVOPTS("rematrix_maxval", "0.5", "rmvol", "0.5"), .clev = M_SQRT1_2, .slev = M_SQRT1_2, .maxval = 0.5, .volume = 0.5, .gain = 0.25, }; MP_NORETURN MP_PRINTF_ATTRIBUTE(1, 2) static void fail(const char *fmt, ...) { va_list va; va_start(va, fmt); vprintf(fmt, va); va_end(va); fflush(stdout); abort(); } static struct mp_chmap parse_map(const char *s) { struct mp_chmap map; if (!mp_chmap_from_str(&map, bstr0(s)) || !mp_chmap_is_valid(&map)) fail("invalid channel map %s\n", s); return map; } // The layout, filled up with NA channels. static struct mp_chmap parse_padded_map(const char *s, int num) { struct mp_chmap map = parse_map(s); while (map.num < num) map.speaker[map.num++] = MP_SPEAKER_ID_NA; return map; } static bool is_passthrough(const struct mp_chmap *in, const struct mp_chmap *out) { return mp_chmap_is_unknown(in) || mp_chmap_is_unknown(out) || mp_chmap_equals(in, out); } // What each output channel has to carry, for an input whose channel c carries // VAL(c + 1), and whether all of them are copies of input channels or silent. // There is no way to convert if this fails. static bool expected_output(const struct mp_chmap *in, const struct mp_chmap *out, const struct mix *mix, double *res, bool *copies) { for (int n = 0; n < out->num; n++) res[n] = 0; *copies = true; // Channels without speaker information keep their position, and so do // all channels if the map does not change. Nothing is done to them, // unless a volume is set. if (is_passthrough(in, out)) { double gain = mix->volume == 1 ? 1 : mix->gain; for (int n = 0; n < MPMIN(in->num, out->num); n++) res[n] = gain * VAL(n + 1); *copies = gain == 1; return true; } // Everything else follows the speakers. If they are the same on both // sides, every speaker gets its own channel, whatever the speakers are. uint64_t in_mask = mp_chmap_to_lavc_unchecked(in); uint64_t out_mask = mp_chmap_to_lavc_unchecked(out); if (in_mask == out_mask) { for (int o = 0; o < out->num; o++) { for (int i = 0; i < in->num; i++) { if (out->speaker[o] != MP_SPEAKER_ID_NA && out->speaker[o] == in->speaker[i]) res[o] = mix->gain * VAL(i + 1); } } *copies = mix->gain == 1; return true; } // Different speakers are mixed. The reference is what libswresample // mixes between the two sets of speakers in its native order, which // knows nothing about the channel order on either side. AVChannelLayout in_layout, out_layout; assert_true(av_channel_layout_from_mask(&in_layout, in_mask) >= 0); assert_true(av_channel_layout_from_mask(&out_layout, out_mask) >= 0); static double matrix[64 * 64]; memset(matrix, 0, sizeof(matrix)); if (swr_build_matrix2(&in_layout, &out_layout, mix->clev, mix->slev, mix->lfe, mix->maxval, mix->volume, matrix, 64, mix->encoding, NULL) < 0) return false; for (int o = 0; o < out->num; o++) { if (out->speaker[o] == MP_SPEAKER_ID_NA) continue; int mo = av_popcount64(out_mask & ((1ULL << out->speaker[o]) - 1)); int sources = 0; for (int i = 0; i < in->num; i++) { if (in->speaker[i] == MP_SPEAKER_ID_NA) continue; int mi = av_popcount64(in_mask & ((1ULL << in->speaker[i]) - 1)); double gain = matrix[mo * 64 + mi]; res[o] += gain * VAL(i + 1); if (gain) { sources++; *copies &= gain == 1; } } *copies &= sources <= 1; } return true; } static void put_sample(uint8_t *dst, int format, double v) { switch (af_fmt_from_planar(format)) { case AF_FORMAT_S16: { int16_t s = lrint(v * 32768.0); memcpy(dst, &s, sizeof(s)); break; } case AF_FORMAT_S32: { int32_t s = lrint(v * 2147483648.0); memcpy(dst, &s, sizeof(s)); break; } case AF_FORMAT_FLOAT: { float f = v; memcpy(dst, &f, sizeof(f)); break; } case AF_FORMAT_DOUBLE: memcpy(dst, &v, sizeof(v)); break; default: abort(); } } static double get_sample(const uint8_t *src, int format) { switch (af_fmt_from_planar(format)) { case AF_FORMAT_S16: { int16_t s; memcpy(&s, src, sizeof(s)); return s / 32768.0; } case AF_FORMAT_S32: { int32_t s; memcpy(&s, src, sizeof(s)); return s / 2147483648.0; } case AF_FORMAT_FLOAT: { float f; memcpy(&f, src, sizeof(f)); return f; } case AF_FORMAT_DOUBLE: { double d; memcpy(&d, src, sizeof(d)); return d; } default: abort(); } } // The value that a sample of this format holds after put_sample(). static double stored_sample(int format, double v) { uint8_t buf[8]; put_sample(buf, format, v); return get_sample(buf, format); } static uint8_t *sample_ptr(struct mp_aframe *frame, uint8_t **data, int ch, int n) { int format = mp_aframe_get_format(frame); size_t sstride = mp_aframe_get_sstride(frame); if (af_fmt_is_planar(format)) return data[ch] + n * sstride; return data[0] + n * sstride + ch * af_fmt_to_bytes(format); } // The frame starts at sample first of the whole input. static struct mp_aframe *make_frame(const struct input *in, int first, bool vary) { struct mp_chmap map = in->spec.map; struct mp_aframe *frame = mp_aframe_create(); assert_true(mp_aframe_set_chmap(frame, &map)); assert_true(mp_aframe_set_format(frame, in->spec.format)); assert_true(mp_aframe_set_rate(frame, in->spec.rate)); assert_true(mp_aframe_alloc_data(frame, in->samples)); mp_aframe_set_pts(frame, 0); uint8_t **data = mp_aframe_get_data_rw(frame); assert_true(data); for (int ch = 0; ch < map.num; ch++) { for (int n = 0; n < in->samples; n++) { double v = VAL(ch + 1) * (vary ? weight(first + n) : 1); put_sample(sample_ptr(frame, data, ch, n), in->spec.format, v); } } return frame; } struct result { bool failed; // the filter gave up int num_samples; double *samples; // num_samples * channels values, interleaved }; // Push the frames through a swresample filter and collect everything it // outputs, driving it the way the player drives a top-level filter. static struct result run_conversion(void *ta_ctx, const struct input *in, int num_in, const struct spec *out, const struct mix *mix, bool vary) { struct mpv_global global = {0}; struct mp_filter *root = mp_filter_create_root(&global); assert_true(root); struct mp_resample_opts opts = MP_RESAMPLE_OPTS_DEF; opts.normalize = mix->normalize; opts.avopts = mix->avopts; struct mp_swresample *sw = mp_swresample_create(root, &opts); assert_true(sw); sw->out_format = out->format; sw->out_rate = out->rate; sw->out_channels = out->map; struct mp_filter *f = sw->f; struct result r = {0}; int next = 0; int first = 0; int reset_done = -1; bool eof_sent = false; for (int iter = 0; ; iter++) { if (iter >= 1000) fail(" the filter neither asks for input nor produces output\n"); if (mp_filter_has_failed(f)) { r.failed = true; break; } if (mp_pin_in_needs_data(f->pins[0])) { if (next < num_in && in[next].reset && reset_done != next) { // The filter asks for input, so it has nothing buffered. mp_filter_reset(f); reset_done = next; continue; } if (next < num_in) { if (in[next].speed) { struct mp_filter_command cmd = { .type = MP_FILTER_COMMAND_SET_SPEED_RESAMPLE, .speed = in[next].speed, }; assert_true(mp_filter_command(f, &cmd)); } struct mp_aframe *frame = make_frame(&in[next], first, vary); first += in[next++].samples; assert_true(mp_pin_in_write(f->pins[0], MAKE_FRAME(MP_FRAME_AUDIO, frame))); } else if (!eof_sent) { assert_true(mp_pin_in_write(f->pins[0], MP_EOF_FRAME)); eof_sent = true; } } if (!mp_pin_out_request_data(f->pins[1])) continue; struct mp_frame frame = mp_pin_out_read(f->pins[1]); if (frame.type == MP_FRAME_EOF) break; assert_int_equal(frame.type, MP_FRAME_AUDIO); struct mp_aframe *aframe = frame.data; struct mp_chmap map; assert_true(mp_aframe_get_chmap(aframe, &map)); if (!mp_chmap_equals(&map, &out->map)) fail(" output channel map is %s\n", mp_chmap_to_str(&map)); assert_int_equal(mp_aframe_get_format(aframe), out->format); assert_int_equal(mp_aframe_get_rate(aframe), out->rate); int count = mp_aframe_get_size(aframe); uint8_t **data = mp_aframe_get_data_ro(aframe); assert_true(data); r.samples = talloc_realloc(ta_ctx, r.samples, double, (r.num_samples + count) * map.num); for (int n = 0; n < count; n++) { for (int ch = 0; ch < map.num; ch++) { r.samples[(r.num_samples + n) * map.num + ch] = get_sample(sample_ptr(aframe, data, ch, n), out->format); } } r.num_samples += count; talloc_free(aframe); } assert_true(r.failed || eof_sent); talloc_free(root); return r; } static bool is_s16(int format) { return af_fmt_from_planar(format) == AF_FORMAT_S16; } static void print_conversion(const struct input *in, int num_in, const struct spec *out, const struct mix *mix) { for (int n = 0; n < num_in; n++) { printf("%s%s %s %d", n ? ", " : "", mp_chmap_to_str(&in[n].spec.map), af_fmt_to_str(in[n].spec.format), in[n].spec.rate); if (in[n].reset) printf(" after a reset"); if (in[n].speed) printf(" at speed %g", in[n].speed); } printf(" -> %s %s %d%s", mp_chmap_to_str(&out->map), af_fmt_to_str(out->format), out->rate, mix->normalize ? " normalized" : ""); for (int n = 0; mix->avopts && mix->avopts[n * 2]; n++) printf(" %s=%s", mix->avopts[n * 2], mix->avopts[n * 2 + 1]); printf("\n"); } // Convert the input frames, which can differ in format, to out and check // every sample that comes out. static void check_conversion(const struct input *in, int num_in, const struct spec *out, const struct mix *mix) { print_conversion(in, num_in, out, mix); bool resample = false; bool s16 = is_s16(out->format); double speed = 1; double total = 0; for (int n = 0; n < num_in; n++) { if (in[n].speed) speed = in[n].speed; resample |= in[n].spec.rate != out->rate || speed != 1; s16 |= is_s16(in[n].spec.format); total += in[n].samples * (double)out->rate / (in[n].spec.rate * speed); } // Resampling changes the sample positions and ramps in and out, so it // gets a constant input, and only the middle of the output is checked. for (int n = 0; resample && n < num_in; n++) assert_true(mp_chmap_equals(&in[n].spec.map, &in[0].spec.map)); double tolerance = resample ? 1e-3 : s16 ? 4e-4 : 1e-6; void *ta_ctx = talloc_new(NULL); struct result r = run_conversion(ta_ctx, in, num_in, out, mix, !resample); if (r.failed) fail(" the filter failed\n"); int first = 0; int last = r.num_samples; if (resample) { // Nothing gets lost, and the rates are met to the sample. if (fabs(r.num_samples - total) > 8) fail(" %d samples, expected %.0f\n", r.num_samples, total); first = r.num_samples / 4; last = r.num_samples - r.num_samples / 4; } else if (r.num_samples != lrint(total)) { fail(" %d samples, expected %.0f\n", r.num_samples, total); } int cur = -1; int cur_end = 0; double expected[MP_NUM_CHANNELS]; bool exact = false; for (int n = first; n < last; n++) { // Without resampling, every input sample yields one output sample. while (cur < 0 || (!resample && n >= cur_end)) { cur++; cur_end += in[cur].samples; if (!expected_output(&in[cur].spec.map, &out->map, mix, expected, &exact)) fail(" there is no reference for this conversion\n"); // Copies within a sample format leave nothing to compute, so // they have to come out bit by bit. exact &= !resample && af_fmt_from_planar(in[cur].spec.format) == af_fmt_from_planar(out->format); } for (int ch = 0; ch < out->map.num; ch++) { double got = r.samples[n * out->map.num + ch]; double want = expected[ch] * (resample ? 1 : weight(n)); if (exact) want = stored_sample(out->format, want); if (exact ? got != want : fabs(got - want) > tolerance) { fail(" channel %d, sample %d: %.10f, expected %.10f%s\n", ch, n, got, want, exact ? " exactly" : ""); } } } talloc_free(ta_ctx); } // There is no way to convert between these, so the filter has to fail. static void check_unsupported(const char *in_map, const char *out_map) { struct input in = {{parse_map(in_map), AF_FORMAT_FLOATP, 48000}, 1000}; struct spec out = {parse_map(out_map), AF_FORMAT_FLOATP, 48000}; print_conversion(&in, 1, &out, &mix_default); double expected[MP_NUM_CHANNELS]; bool copies; if (expected_output(&in.spec.map, &out.map, &mix_default, expected, &copies)) fail(" there is a reference for this conversion\n"); void *ta_ctx = talloc_new(NULL); struct result r = run_conversion(ta_ctx, &in, 1, &out, &mix_default, true); if (!r.failed) fail(" the filter did not fail\n"); talloc_free(ta_ctx); } // The reference itself, against values written down by hand. static void check_reference(void) { static const struct { const char *in; const char *out; double expected[6]; const struct mix *mix; // NULL for mix_default bool mixed; } refs[] = { {"5.1", "stereo", {VAL(1) + M_SQRT1_2 * (VAL(3) + VAL(5)), VAL(2) + M_SQRT1_2 * (VAL(3) + VAL(6))}, .mixed = true}, {"7.1", "stereo", {VAL(1) + M_SQRT1_2 * (VAL(3) + VAL(5) + VAL(7)), VAL(2) + M_SQRT1_2 * (VAL(3) + VAL(6) + VAL(8))}, .mixed = true}, {"mono", "stereo", {M_SQRT1_2 * VAL(1), M_SQRT1_2 * VAL(1)}, .mixed = true}, {"stereo", "5.1", {VAL(1), VAL(2)}}, {"5.1(side)", "5.1", {VAL(1), VAL(2), VAL(3), VAL(4), VAL(5), VAL(6)}}, {"5.1", "5.1(alsa)", {VAL(1), VAL(2), VAL(5), VAL(6), VAL(3), VAL(4)}}, // Not their own inverse, so the direction of the mapping shows. {"fc-fl-fr-bl-br-lfe", "5.1", {VAL(2), VAL(3), VAL(1), VAL(6), VAL(4), VAL(5)}}, {"5.1", "fc-fl-fr-bl-br-lfe", {VAL(3), VAL(1), VAL(2), VAL(5), VAL(6), VAL(4)}}, {"fc-fl-fr-bl-br-lfe", "stereo", {VAL(2) + M_SQRT1_2 * (VAL(1) + VAL(4)), VAL(3) + M_SQRT1_2 * (VAL(1) + VAL(5))}, .mixed = true}, {"5.1", "na-fl-fr-na", {0, VAL(1) + M_SQRT1_2 * (VAL(3) + VAL(5)), VAL(2) + M_SQRT1_2 * (VAL(3) + VAL(6)), 0}, .mixed = true}, {"unknown9", "stereo", {VAL(1), VAL(2)}}, {"stereo", "unknown6", {VAL(1), VAL(2)}}, {"fl-fr-na", "fl-fr-na", {VAL(1), VAL(2), VAL(3)}}, // The sum of the coefficients of a channel is limited to 1. {"5.1", "stereo", {(VAL(1) + M_SQRT1_2 * (VAL(3) + VAL(5))) / (1 + 2 * M_SQRT1_2), (VAL(2) + M_SQRT1_2 * (VAL(3) + VAL(6))) / (1 + 2 * M_SQRT1_2)}, .mix = &mix_normalize, .mixed = true}, // A single speaker is mono, whatever it is. {"fl-na", "stereo", {M_SQRT1_2 * VAL(1), M_SQRT1_2 * VAL(1)}, .mixed = true}, {"stereo", "na-fr", {0, M_SQRT1_2 * (VAL(1) + VAL(2))}, .mixed = true}, // The downmix pair is stereo. {"dr-dl", "stereo", {VAL(2), VAL(1)}}, {"5.1", "dr-na-dl", {VAL(2) + M_SQRT1_2 * (VAL(3) + VAL(6)), 0, VAL(1) + M_SQRT1_2 * (VAL(3) + VAL(5))}, .mixed = true}, // The same speakers need no mix, not even those that have none. {"bl-br", "br-na-bl", {VAL(2), 0, VAL(1)}}, {"fc-na-fl", "fl-fc", {VAL(3), VAL(1)}}, // A mix can leave every channel in its place. {"fc-bl-br", "fc-fl-fr", {VAL(1), M_SQRT1_2 * VAL(2), M_SQRT1_2 * VAL(3)}, .mixed = true}, // Two channels can go into one at full level. {"7.1(wide)", "5.1", {VAL(1) + VAL(7), VAL(2) + VAL(8), VAL(3), VAL(4), VAL(5), VAL(6)}, .mixed = true}, // Every kind of channel has its level, and the volume is for all. {"5.1", "stereo", {0.5 * (VAL(1) + 0.5 * VAL(3) + 0.75 * M_SQRT1_2 * VAL(4) + 0.25 * VAL(5)), 0.5 * (VAL(2) + 0.5 * VAL(3) + 0.75 * M_SQRT1_2 * VAL(4) + 0.25 * VAL(6))}, .mix = &mix_levels, .mixed = true}, {"5.1", "5.1(alsa)", {0.5 * VAL(1), 0.5 * VAL(2), 0.5 * VAL(5), 0.5 * VAL(6), 0.5 * VAL(3), 0.5 * VAL(4)}, .mix = &mix_levels, .mixed = true}, {"unknown9", "stereo", {0.5 * VAL(1), 0.5 * VAL(2)}, .mix = &mix_levels, .mixed = true}, // The limit is for the mix, the volume comes on top of it. {"5.1", "stereo", {2 * (VAL(1) + M_SQRT1_2 * VAL(3) - M_SQRT1_2 * M_SQRT1_2 * (VAL(5) + VAL(6))) / (2 + M_SQRT1_2), 2 * (VAL(2) + M_SQRT1_2 * VAL(3) + M_SQRT1_2 * M_SQRT1_2 * (VAL(5) + VAL(6))) / (2 + M_SQRT1_2)}, .mix = &mix_dolby, .mixed = true}, {"5.1", "5.1(alsa)", {2 * VAL(1), 2 * VAL(2), 2 * VAL(5), 2 * VAL(6), 2 * VAL(3), 2 * VAL(4)}, .mix = &mix_dolby, .mixed = true}, // A negative volume takes the place of the sum of the coefficients. {"5.1", "stereo", {(VAL(1) + M_SQRT1_2 * (VAL(3) + VAL(5))) / 2, (VAL(2) + M_SQRT1_2 * (VAL(3) + VAL(6))) / 2}, .mix = &mix_negative, .mixed = true}, // A limit below 1 lowers channels that change their place, and leaves // those alone that keep their position. {"5.1", "5.1(alsa)", {0.5 * VAL(1), 0.5 * VAL(2), 0.5 * VAL(5), 0.5 * VAL(6), 0.5 * VAL(3), 0.5 * VAL(4)}, .mix = &mix_limit, .mixed = true}, {"unknown9", "stereo", {VAL(1), VAL(2)}, .mix = &mix_limit}, {"5.1", "5.1", {VAL(1), VAL(2), VAL(3), VAL(4), VAL(5), VAL(6)}, .mix = &mix_limit}, }; for (int n = 0; n < MP_ARRAY_SIZE(refs); n++) { struct mp_chmap in = parse_map(refs[n].in); struct mp_chmap out = parse_map(refs[n].out); const struct mix *mix = refs[n].mix ? refs[n].mix : &mix_default; double expected[MP_NUM_CHANNELS]; bool copies; assert_true(expected_output(&in, &out, mix, expected, &copies)); if (copies == refs[n].mixed) { fail("reference %s -> %s is %s\n", refs[n].in, refs[n].out, copies ? "not mixed" : "mixed"); } assert_true(out.num <= MP_ARRAY_SIZE(refs[n].expected)); for (int ch = 0; ch < out.num; ch++) { if (fabs(expected[ch] - refs[n].expected[ch]) > 1e-9) { fail("reference %s -> %s, channel %d: %f, expected %f\n", refs[n].in, refs[n].out, ch, expected[ch], refs[n].expected[ch]); } } } } #define MAP_5_1_4 "fl-fr-fc-lfe-bl-br-tfl-tfr-tbl-tbr" #define MAP_7_1_4 "fl-fr-fc-lfe-bl-br-sl-sr-tfl-tfr-tbl-tbr" #define MAP_9_1_4 "fl-fr-fc-lfe-bl-br-flc-frc-sl-sr-tfl-tfr-tbl-tbr" struct conv_case { const char *in_map; int in_format; int in_rate; const char *out_map; // NULL keeps the input channel map int out_format; int out_rate; bool normalize; }; static const struct conv_case cases[] = { // The same channel map on both sides. {"7.1", AF_FORMAT_FLOATP, 48000, NULL, AF_FORMAT_S16, 48000}, {"2.1", AF_FORMAT_FLOATP, 48000, NULL, AF_FORMAT_S16, 48000}, {"quad", AF_FORMAT_FLOAT, 48000, NULL, AF_FORMAT_S16, 48000}, {"5.0", AF_FORMAT_S16, 48000, NULL, AF_FORMAT_FLOATP, 48000}, {"6.1", AF_FORMAT_FLOATP, 48000, NULL, AF_FORMAT_S32, 48000}, {"unknown8", AF_FORMAT_FLOATP, 48000, NULL, AF_FORMAT_S16, 48000}, {"unknown9", AF_FORMAT_S16, 48000, NULL, AF_FORMAT_FLOATP, 48000}, {"fl-fr-na", AF_FORMAT_FLOATP, 48000, NULL, AF_FORMAT_S16, 48000}, {"fc-fl-fr-bl-br-lfe", AF_FORMAT_S16, 48000, NULL, AF_FORMAT_FLOAT, 48000}, {"22.2", AF_FORMAT_S32, 48000, NULL, AF_FORMAT_S32P, 48000}, {"7.1", AF_FORMAT_FLOATP, 48000, NULL, AF_FORMAT_FLOATP, 44100}, // Unknown layouts. {"unknown8", AF_FORMAT_FLOATP, 48000, "7.1", AF_FORMAT_S16, 48000}, {"7.1", AF_FORMAT_S16, 48000, "unknown8", AF_FORMAT_FLOATP, 48000}, {"unknown9", AF_FORMAT_FLOATP, 48000, "stereo", AF_FORMAT_S16, 48000}, {"stereo", AF_FORMAT_S16, 48000, "unknown9", AF_FORMAT_FLOATP, 48000}, {"unknown16", AF_FORMAT_FLOATP, 48000, "unknown6", AF_FORMAT_FLOATP, 48000}, {"unknown9", AF_FORMAT_S32, 48000, "stereo", AF_FORMAT_S32, 48000}, {"unknown9", AF_FORMAT_S16, 48000, "stereo", AF_FORMAT_S16, 96000}, // Reordering between the same speakers. {"5.1", AF_FORMAT_S16, 48000, "5.1(alsa)", AF_FORMAT_S16, 48000}, {"5.1", AF_FORMAT_S32, 48000, "5.1(alsa)", AF_FORMAT_S32, 48000}, {"5.1", AF_FORMAT_DOUBLEP, 48000, "5.1(alsa)", AF_FORMAT_DOUBLEP, 48000}, {"7.1", AF_FORMAT_FLOATP, 48000, "7.1(alsa)", AF_FORMAT_S16, 48000}, {"7.1(alsa)", AF_FORMAT_S16, 48000, "7.1", AF_FORMAT_FLOATP, 48000}, {"7.1(alsa)", AF_FORMAT_S32P, 48000, "7.1", AF_FORMAT_S32P, 48000}, {"fc-fl-fr-bl-br-lfe", AF_FORMAT_FLOATP, 48000, "5.1", AF_FORMAT_FLOATP, 48000}, {"fc-fl-fr-bl-br-lfe", AF_FORMAT_S16, 48000, "5.1", AF_FORMAT_S16, 48000}, {"5.1", AF_FORMAT_FLOATP, 48000, "fc-fl-fr-bl-br-lfe", AF_FORMAT_FLOATP, 48000}, {"5.1", AF_FORMAT_S16, 48000, "fc-fl-fr-bl-br-lfe", AF_FORMAT_S16, 48000}, {"fc-fl-fr-bl-br-lfe", AF_FORMAT_FLOAT, 48000, "lfe-fl-br-fc-bl-fr", AF_FORMAT_S16, 48000}, {"fc-fl-fr-bl-br-lfe", AF_FORMAT_FLOATP, 48000, "5.1", AF_FORMAT_FLOATP, 44100}, {"7.1", AF_FORMAT_FLOATP, 48000, "7.1(alsa)", AF_FORMAT_FLOATP, 44100}, {"22.2", AF_FORMAT_S32, 48000, "bfr-bfl-bfc-tsr-tsl-lfe2-tbr-tbc-tbl-tfr-tfc-tfl-tc-sr-sl-bc-frc-flc-" "br-bl-lfe-fc-fr-fl", AF_FORMAT_S32, 48000}, // libswresample has no mix for these speakers, and needs none. {"bl-br", AF_FORMAT_S32, 48000, "br-bl", AF_FORMAT_S32, 48000}, {"fc-fl", AF_FORMAT_FLOATP, 48000, "fl-na-fc", AF_FORMAT_S16, 48000}, {"dr-dl", AF_FORMAT_S16, 48000, "dl-dr", AF_FORMAT_S16, 48000}, // NA channels in the output, as padded device layouts have them. {"5.1", AF_FORMAT_FLOATP, 48000, "fl-fr-fc-lfe-bl-br-na-na", AF_FORMAT_S16, 48000}, {"5.1", AF_FORMAT_S32, 48000, "fl-fr-fc-lfe-bl-br-na-na", AF_FORMAT_S32, 48000}, {"5.1", AF_FORMAT_FLOATP, 48000, "na-fc-fl-na-fr-bl-br-lfe", AF_FORMAT_FLOATP, 48000}, {"fc-fl-fr-bl-br-lfe", AF_FORMAT_S16, 48000, "fl-na-fr-bl-br-fc-lfe-na", AF_FORMAT_S16, 48000}, {"stereo", AF_FORMAT_S16, 48000, "fl-fr-na-na", AF_FORMAT_S16, 48000}, {"stereo", AF_FORMAT_S32P, 48000, "fl-fr-na-na", AF_FORMAT_S32P, 48000}, {"5.1", AF_FORMAT_FLOATP, 48000, "fl-fr-na-na", AF_FORMAT_FLOATP, 48000}, {"5.1(side)", AF_FORMAT_FLOATP, 48000, "fl-fr-fc-lfe-bl-br-na-na", AF_FORMAT_S16, 48000}, {"5.1", AF_FORMAT_FLOATP, 44100, "fl-fr-fc-lfe-bl-br-na-na", AF_FORMAT_S16, 48000}, // Remixing. {"5.1(side)", AF_FORMAT_FLOATP, 48000, "5.1", AF_FORMAT_FLOATP, 48000}, {"5.1", AF_FORMAT_FLOATP, 48000, "stereo", AF_FORMAT_FLOATP, 48000}, {"7.1", AF_FORMAT_FLOATP, 48000, "stereo", AF_FORMAT_FLOATP, 48000}, {"7.1", AF_FORMAT_S16, 48000, "stereo", AF_FORMAT_S16, 48000}, {"stereo", AF_FORMAT_FLOATP, 48000, "5.1", AF_FORMAT_FLOATP, 48000}, {"mono", AF_FORMAT_FLOATP, 48000, "stereo", AF_FORMAT_FLOATP, 48000}, {"fc-fl-fr-bl-br-lfe", AF_FORMAT_S16, 48000, "stereo", AF_FORMAT_FLOATP, 48000}, {"stereo", AF_FORMAT_FLOATP, 48000, "fc-fl-fr-bl-br-lfe", AF_FORMAT_S16, 48000}, {"fc-fl-fr-bl-br-lfe", AF_FORMAT_FLOATP, 48000, "fr-na-fl", AF_FORMAT_FLOATP, 44100}, // Every channel stays in its place. {"fc-bl-br", AF_FORMAT_FLOATP, 48000, "fc-fl-fr", AF_FORMAT_S16, 48000}, {"fc-bl-br", AF_FORMAT_S32, 48000, "fc-fl-fr", AF_FORMAT_S32, 48000}, {"fc-sl-sr", AF_FORMAT_S16, 48000, "fc-fl-fr", AF_FORMAT_FLOATP, 44100}, // Two channels go into one at full level. {"7.1(wide)", AF_FORMAT_S32, 48000, "5.1", AF_FORMAT_S32, 48000}, // With the sum of the coefficients limited. {"5.1", AF_FORMAT_FLOATP, 48000, "stereo", AF_FORMAT_FLOATP, 48000, true}, {"7.1", AF_FORMAT_S16, 48000, "stereo", AF_FORMAT_S16, 48000, true}, {"fc-fl-fr-bl-br-lfe", AF_FORMAT_FLOAT, 48000, "fr-na-fl", AF_FORMAT_S32, 48000, true}, {"22.2", AF_FORMAT_FLOATP, 48000, "5.1(alsa)", AF_FORMAT_S16, 48000, true}, {"5.1", AF_FORMAT_S32, 48000, "5.1(alsa)", AF_FORMAT_S32, 48000, true}, // More speakers than 7.1 has. {"7.1(top)", AF_FORMAT_FLOATP, 48000, "5.1", AF_FORMAT_FLOATP, 48000}, {MAP_5_1_4, AF_FORMAT_FLOATP, 48000, "stereo", AF_FORMAT_S16, 48000}, {MAP_7_1_4, AF_FORMAT_FLOATP, 48000, "7.1(alsa)", AF_FORMAT_S32, 48000}, {MAP_7_1_4, AF_FORMAT_S16, 48000, MAP_5_1_4, AF_FORMAT_FLOATP, 48000}, {MAP_9_1_4, AF_FORMAT_FLOATP, 48000, "fl-fr-fc-lfe-bl-br-na-na", AF_FORMAT_S16, 48000}, {"5.1", AF_FORMAT_FLOATP, 48000, MAP_7_1_4, AF_FORMAT_FLOATP, 48000}, {"cube", AF_FORMAT_FLOATP, 48000, "quad", AF_FORMAT_FLOATP, 48000}, {"hexadecagonal", AF_FORMAT_FLOATP, 48000, "7.1", AF_FORMAT_S16, 48000}, {"22.2", AF_FORMAT_FLOATP, 48000, "5.1", AF_FORMAT_FLOATP, 48000}, {"22.2", AF_FORMAT_S16, 48000, MAP_7_1_4, AF_FORMAT_S16, 44100}, // A single speaker, and the downmix pair. {"fl-na", AF_FORMAT_FLOATP, 48000, "stereo", AF_FORMAT_FLOATP, 48000}, {"na-bl-na", AF_FORMAT_S16, 48000, "5.1", AF_FORMAT_S16, 48000}, {"stereo", AF_FORMAT_FLOATP, 48000, "na-fr", AF_FORMAT_FLOATP, 48000}, {"5.1", AF_FORMAT_FLOATP, 48000, "lfe-na-na-na", AF_FORMAT_S16, 48000}, {"sl", AF_FORMAT_FLOATP, 48000, "na-fl-fr", AF_FORMAT_FLOATP, 48000}, {"dr-dl", AF_FORMAT_FLOATP, 48000, "stereo", AF_FORMAT_FLOATP, 48000}, {"dl-dr-na", AF_FORMAT_S16, 48000, "5.1(alsa)", AF_FORMAT_S16, 48000}, {"5.1", AF_FORMAT_FLOATP, 48000, "dr-na-dl", AF_FORMAT_FLOATP, 48000}, {"7.1", AF_FORMAT_FLOATP, 48000, "dl-dr", AF_FORMAT_S16, 44100}, // NA channels in the input. {"fl-fr-na", AF_FORMAT_FLOATP, 48000, "stereo", AF_FORMAT_S16, 48000}, {"na-fl-fr", AF_FORMAT_S16, 48000, "stereo", AF_FORMAT_S16, 48000}, {"fl-fr-na-na", AF_FORMAT_S32, 48000, "stereo", AF_FORMAT_S32, 48000}, {"fc-na-fl-fr-bl-br-lfe", AF_FORMAT_S16, 48000, "5.1(alsa)", AF_FORMAT_FLOATP, 48000}, }; static void check_case(const struct conv_case *c) { struct spec in = {parse_map(c->in_map), c->in_format, c->in_rate}; struct spec out = {in.map, c->out_format, c->out_rate}; if (c->out_map) out.map = parse_map(c->out_map); const struct mix *mix = c->normalize ? &mix_normalize : &mix_default; if (in.rate != out.rate) { check_conversion(&(struct input){in, 4000}, 1, &out, mix); return; } // Several frames, and none of them matches the size of the output frames. struct input frames[] = {{in, 1500}, {in, 7}, {in, 2493}}; check_conversion(frames, MP_ARRAY_SIZE(frames), &out, mix); } static unsigned rnd(unsigned *state) { *state = *state * 1103515245u + 12345u; return (*state >> 16) & 0x7fff; } static void shuffle(struct mp_chmap *map, unsigned *state) { for (int n = map->num - 1; n > 0; n--) { int i = rnd(state) % (n + 1); MPSWAP(uint8_t, map->speaker[n], map->speaker[i]); } } static void pad_na(struct mp_chmap *map, unsigned *state) { int count = 1 + rnd(state) % 2; for (int n = 0; n < count; n++) { int pos = rnd(state) % (map->num + 1); memmove(&map->speaker[pos + 1], &map->speaker[pos], map->num - pos); map->speaker[pos] = MP_SPEAKER_ID_NA; map->num++; } } // Every pair of these layouts, with the channels of both sides in a random // order, without NA channels, with some in the output, and with some on both // sides. static void check_shuffled(const struct mix *mix) { static const char *const layouts[] = { "mono", "stereo", "2.1", "3.0", "quad", "5.0", "5.1", "5.1(side)", "6.1", "7.1", "7.1(top)", "cube", MAP_7_1_4, "hexadecagonal", "22.2", "sl", "dl-dr", }; static const int formats[] = { AF_FORMAT_S16, AF_FORMAT_FLOATP, AF_FORMAT_S32, AF_FORMAT_FLOAT, AF_FORMAT_S16P, AF_FORMAT_DOUBLEP, }; unsigned state = 1; for (int i = 0; i < MP_ARRAY_SIZE(layouts); i++) { for (int o = 0; o < MP_ARRAY_SIZE(layouts); o++) { for (int na = 0; na < 3; na++) { struct input in = { .spec = { .map = parse_map(layouts[i]), .format = formats[rnd(&state) % MP_ARRAY_SIZE(formats)], .rate = 48000, }, .samples = 3000, }; struct spec out = { .map = parse_map(layouts[o]), .format = formats[rnd(&state) % MP_ARRAY_SIZE(formats)], .rate = 48000, }; shuffle(&in.spec.map, &state); shuffle(&out.map, &state); if (na > 0) pad_na(&out.map, &state); if (na > 1) pad_na(&in.spec.map, &state); check_conversion(&in, 1, &out, mix); } } } } // As many channels as a map can have. static void check_wide(void) { static const char *const maps[][2] = { {"stereo", "stereo"}, {"5.1", "stereo"}, {"5.1", "7.1(alsa)"}, {"fc-fl-fr-bl-br-lfe", "5.1"}, {"22.2", "5.1"}, }; static const int formats[] = {AF_FORMAT_FLOATP, AF_FORMAT_S16}; for (int n = 0; n < MP_ARRAY_SIZE(maps); n++) { for (int f = 0; f < MP_ARRAY_SIZE(formats); f++) { struct spec narrow_in = {parse_map(maps[n][0]), formats[f], 48000}; struct spec narrow_out = {parse_map(maps[n][1]), formats[f], 48000}; struct spec wide_in = narrow_in; struct spec wide_out = narrow_out; wide_in.map = parse_padded_map(maps[n][0], MP_NUM_CHANNELS); wide_out.map = parse_padded_map(maps[n][1], MP_NUM_CHANNELS); check_conversion(&(struct input){narrow_in, 3000}, 1, &wide_out, &mix_default); check_conversion(&(struct input){wide_in, 3000}, 1, &narrow_out, &mix_default); check_conversion(&(struct input){wide_in, 3000}, 1, &wide_out, &mix_default); } } struct spec unknown = {.format = AF_FORMAT_S32, .rate = 48000}; mp_chmap_set_unknown(&unknown.map, MP_NUM_CHANNELS); struct spec stereo = {parse_map("stereo"), AF_FORMAT_S32, 48000}; struct spec surround = {parse_map("7.1"), AF_FORMAT_S32, 48000}; const struct mix *mix = &mix_default; check_conversion(&(struct input){unknown, 3000}, 1, &unknown, mix); check_conversion(&(struct input){unknown, 3000}, 1, &stereo, mix); check_conversion(&(struct input){unknown, 3000}, 1, &surround, mix); check_conversion(&(struct input){stereo, 3000}, 1, &unknown, mix); } // The input format changes while the filter is running. static void check_reconfigure(void) { struct input frames[] = { {{parse_map("5.1"), AF_FORMAT_FLOATP, 48000}, 1000}, {{parse_map("fc-fl-fr-bl-br-lfe"), AF_FORMAT_S16, 48000}, 777}, {{parse_map("stereo"), AF_FORMAT_FLOAT, 48000}, 2500}, {{parse_map("7.1(alsa)"), AF_FORMAT_S16, 48000}, 300}, {{parse_map("unknown8"), AF_FORMAT_S32, 48000}, 1921}, {{parse_map("sl-na"), AF_FORMAT_FLOATP, 48000}, 40}, {{parse_map(MAP_7_1_4), AF_FORMAT_FLOATP, 48000}, 2000}, {{parse_map("lfe-br-bl-fc-fr-fl"), AF_FORMAT_S16, 48000}, 1234}, }; struct spec out = { parse_map("fl-fr-na-bl-br-fc-lfe-na"), AF_FORMAT_S16, 48000, }; check_conversion(frames, MP_ARRAY_SIZE(frames), &out, &mix_default); // Only the channels change, and copies keep their samples. for (int n = 0; n < MP_ARRAY_SIZE(frames); n++) frames[n].spec.format = AF_FORMAT_S32; out.format = AF_FORMAT_S32; check_conversion(frames, MP_ARRAY_SIZE(frames), &out, &mix_default); } static const char *const stream_maps[][2] = { {"fc-fl-fr-bl-br-lfe", "5.1"}, {"5.1", "fl-na-fr-bl-br-fc-lfe-na"}, {"fc-fl-fr-bl-br-lfe", "stereo"}, {"7.1", "7.1"}, {"unknown9", "stereo"}, {"dr-na-dl", "fl-fr-na-na"}, }; // Copies of s32 take another way through libswresample than all others. static const int stream_formats[][2] = { {AF_FORMAT_FLOATP, AF_FORMAT_FLOATP}, {AF_FORMAT_S16, AF_FORMAT_FLOATP}, {AF_FORMAT_S32, AF_FORMAT_S32}, }; // A reset must not change what the filter does with the channels. For // libswresample this is swr_close() and swr_init() on the same context. static void check_reset(void) { for (int n = 0; n < MP_ARRAY_SIZE(stream_maps); n++) { for (int f = 0; f < MP_ARRAY_SIZE(stream_formats); f++) { struct spec in = { parse_map(stream_maps[n][0]), stream_formats[f][0], 48000, }; struct spec out = { parse_map(stream_maps[n][1]), stream_formats[f][1], 48000, }; struct input frames[] = {{in, 2500}, {in, 1500, .reset = true}}; check_conversion(frames, MP_ARRAY_SIZE(frames), &out, &mix_default); } } } // Neither must a change of the playback speed. A small one makes // libswresample initialize its context again to compensate, a large one makes // the filter set up a new context. static void check_speed(void) { static const double speeds[][2] = { {1, 1.005}, {1, 0.995}, {1, 1.5}, {1.25, 1}, {2, 2.01}, {0.5, 0}, }; for (int n = 0; n < MP_ARRAY_SIZE(stream_maps); n++) { for (int f = 0; f < MP_ARRAY_SIZE(stream_formats); f++) { struct spec in = { parse_map(stream_maps[n][0]), stream_formats[f][0], 48000, }; struct spec out = { parse_map(stream_maps[n][1]), stream_formats[f][1], 48000, }; for (int s = 0; s < MP_ARRAY_SIZE(speeds); s++) { // The speed changes early, what is checked comes later. struct input frames[] = { {in, 600, .speed = speeds[s][0]}, {in, 9000, .speed = speeds[s][1]}, {in, 3000}, }; check_conversion(frames, MP_ARRAY_SIZE(frames), &out, &mix_default); } } } } struct opt_case { const char *in_map; int in_format; const char *out_map; int out_format; const struct mix *mix; }; static const struct opt_case opt_cases[] = { // A volume where channels keep their position. libswresample knows no // layout of 9 channels that it could mix on its own. {"unknown9", AF_FORMAT_FLOATP, "stereo", AF_FORMAT_FLOATP, &mix_half}, {"stereo", AF_FORMAT_S32, "unknown9", AF_FORMAT_S32, &mix_half}, {"unknown9", AF_FORMAT_S32, "unknown9", AF_FORMAT_S32, &mix_half}, {"7.1", AF_FORMAT_FLOATP, "7.1", AF_FORMAT_S16, &mix_half}, {"5.1", AF_FORMAT_FLOATP, "stereo", AF_FORMAT_FLOATP, &mix_negative}, {"5.1", AF_FORMAT_FLOATP, "5.1(alsa)", AF_FORMAT_S16, &mix_negative}, {"fc-fl-fr-bl-br-lfe", AF_FORMAT_S32, "5.1", AF_FORMAT_S32, &mix_negative}, // The level stays, so copies keep their samples. {"unknown9", AF_FORMAT_S32, "unknown9", AF_FORMAT_S32, &mix_unity}, {"stereo", AF_FORMAT_S32, "unknown9", AF_FORMAT_S32, &mix_unity}, {"5.1", AF_FORMAT_S32, "5.1(alsa)", AF_FORMAT_S32, &mix_unity}, {"5.1(side)", AF_FORMAT_S32, "5.1", AF_FORMAT_S32, &mix_unity}, // The limit that libswresample picks for the sample formats. {"5.1", AF_FORMAT_FLOATP, "stereo", AF_FORMAT_FLOATP, &mix_auto_float}, {"5.1", AF_FORMAT_S16, "stereo", AF_FORMAT_DOUBLEP, &mix_auto_float}, {"5.1", AF_FORMAT_FLOATP, "stereo", AF_FORMAT_S16, &mix_auto_integer}, {"5.1", AF_FORMAT_S32, "stereo", AF_FORMAT_S32, &mix_auto_integer}, {"5.1", AF_FORMAT_S16, "stereo", AF_FORMAT_FLOATP, &mix_auto_s16p}, {"5.1", AF_FORMAT_S16, "stereo", AF_FORMAT_S16, &mix_auto_s16p}, {"5.1", AF_FORMAT_S16, "stereo", AF_FORMAT_FLOATP, &mix_auto_fltp_float}, {"5.1", AF_FORMAT_S16, "stereo", AF_FORMAT_S16, &mix_auto_fltp_integer}, // Without a volume, channels that keep their position are left alone. {"5.1", AF_FORMAT_FLOATP, "stereo", AF_FORMAT_FLOATP, &mix_limit}, {"5.1", AF_FORMAT_FLOATP, "5.1(alsa)", AF_FORMAT_FLOATP, &mix_limit}, {"fc-fl-fr-bl-br-lfe", AF_FORMAT_S32, "5.1", AF_FORMAT_S32, &mix_limit}, {"unknown9", AF_FORMAT_S32, "stereo", AF_FORMAT_S32, &mix_limit}, {"7.1", AF_FORMAT_S32, "7.1", AF_FORMAT_S32, &mix_limit}, {"5.1", AF_FORMAT_FLOATP, "stereo", AF_FORMAT_FLOATP, &mix_limit_half}, {"5.1", AF_FORMAT_FLOATP, "5.1(alsa)", AF_FORMAT_FLOATP, &mix_limit_half}, {"unknown9", AF_FORMAT_S32, "stereo", AF_FORMAT_S32, &mix_limit_half}, }; // The options of libswresample that change what comes out. static void check_options(void) { check_shuffled(&mix_levels); check_shuffled(&mix_dolby); for (int n = 0; n < MP_ARRAY_SIZE(opt_cases); n++) { const struct opt_case *c = &opt_cases[n]; struct spec in = {parse_map(c->in_map), c->in_format, 48000}; struct spec out = {parse_map(c->out_map), c->out_format, 48000}; struct input frames[] = {{in, 1500}, {in, 7}, {in, 2493}}; check_conversion(frames, MP_ARRAY_SIZE(frames), &out, c->mix); // Neither a reset nor a change of the speed must change them. struct input reset[] = {{in, 2500}, {in, 1500, .reset = true}}; check_conversion(reset, MP_ARRAY_SIZE(reset), &out, c->mix); struct input speed[] = { {in, 600, .speed = 1}, {in, 9000, .speed = 1.005}, {in, 3000}, }; check_conversion(speed, MP_ARRAY_SIZE(speed), &out, c->mix); } } int main(void) { unsigned lib = swresample_version(); printf("libswresample %u.%u.%u, built for %u.%u.%u\n", AV_VERSION_MAJOR(lib), AV_VERSION_MINOR(lib), AV_VERSION_MICRO(lib), LIBSWRESAMPLE_VERSION_MAJOR, LIBSWRESAMPLE_VERSION_MINOR, LIBSWRESAMPLE_VERSION_MICRO); check_reference(); for (int n = 0; n < MP_ARRAY_SIZE(cases); n++) check_case(&cases[n]); check_shuffled(&mix_default); check_wide(); check_reconfigure(); check_reset(); check_speed(); check_options(); // Speakers that libswresample has no mix for. check_unsupported("fl-fc", "stereo"); check_unsupported("bl-br", "stereo"); check_unsupported("stereo", "sl-sr-na"); check_unsupported("dr-dl", "dl-dr-fc"); return 0; }