diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | audio/chmap.c | 478 | ||||
-rw-r--r-- | audio/chmap.h | 133 | ||||
-rw-r--r-- | audio/filter/af.h | 5 | ||||
-rw-r--r-- | core/m_option.c | 42 | ||||
-rw-r--r-- | core/m_option.h | 4 |
6 files changed, 660 insertions, 3 deletions
@@ -122,6 +122,7 @@ ifeq ($(HAVE_AVUTIL_REFCOUNTING),no) endif SOURCES = talloc.c \ + audio/chmap.c \ audio/fmt-conversion.c \ audio/format.c \ audio/mixer.c \ diff --git a/audio/chmap.c b/audio/chmap.c new file mode 100644 index 0000000000..61df408e02 --- /dev/null +++ b/audio/chmap.c @@ -0,0 +1,478 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <assert.h> + +#include "core/mp_msg.h" +#include "chmap.h" + +// Names taken from libavutil/channel_layout.c (Not accessible by API.) +// Use of these names is hard-coded in some places (e.g. ao_alsa.c) +static const char *speaker_names[MP_SPEAKER_ID_COUNT][2] = { + [MP_SPEAKER_ID_FL] = {"fl", "front left"}, + [MP_SPEAKER_ID_FR] = {"fr", "front right"}, + [MP_SPEAKER_ID_FC] = {"fc", "front center"}, + [MP_SPEAKER_ID_LFE] = {"lfe", "low frequency"}, + [MP_SPEAKER_ID_BL] = {"bl", "back left"}, + [MP_SPEAKER_ID_BR] = {"br", "back right"}, + [MP_SPEAKER_ID_FLC] = {"flc", "front left-of-center"}, + [MP_SPEAKER_ID_FRC] = {"frc", "front right-of-center"}, + [MP_SPEAKER_ID_BC] = {"bc", "back center"}, + [MP_SPEAKER_ID_SL] = {"sl", "side left"}, + [MP_SPEAKER_ID_SR] = {"sr", "side right"}, + [MP_SPEAKER_ID_TC] = {"tc", "top center"}, + [MP_SPEAKER_ID_TFL] = {"tfl", "top front left"}, + [MP_SPEAKER_ID_TFC] = {"tfc", "top front center"}, + [MP_SPEAKER_ID_TFR] = {"tfr", "top front right"}, + [MP_SPEAKER_ID_TBL] = {"tbl", "top back left"}, + [MP_SPEAKER_ID_TBC] = {"tbc", "top back center"}, + [MP_SPEAKER_ID_TBR] = {"tbr", "top back right"}, + [MP_SPEAKER_ID_DL] = {"dl", "downmix left"}, + [MP_SPEAKER_ID_DR] = {"dr", "downmix right"}, + [MP_SPEAKER_ID_WL] = {"wl", "wide left"}, + [MP_SPEAKER_ID_WR] = {"wr", "wide right"}, + [MP_SPEAKER_ID_SDL] = {"sdl", "surround direct left"}, + [MP_SPEAKER_ID_SDR] = {"sdr", "surround direct right"}, + [MP_SPEAKER_ID_LFE2] = {"lfe2", "low frequency 2"}, +}; + +// Names taken from libavutil/channel_layout.c (Not accessible by API.) +// Channel order corresponds to lavc/waveex, except for the alsa entries. +static const char *std_layout_names[][2] = { + {"empty", ""}, // not in lavc + {"mono", "fc"}, + {"stereo", "fl-fr"}, + {"2.1", "fl-fr-lfe"}, + {"3.0", "fl-fr-fc"}, + {"3.0(back)", "fl-fr-bc"}, + {"4.0", "fl-fr-fc-bc"}, + {"quad", "fl-fr-bl-br"}, + {"quad(side)", "fl-fr-sl-sr"}, + {"3.1", "fl-fr-fc-lfe"}, + {"5.0", "fl-fr-fc-bl-br"}, + {"5.0(alsa)", "fl-fr-bl-br-fc"}, // not in lavc + {"5.0(side)", "fl-fr-fc-sl-sr"}, + {"4.1", "fl-fr-fc-lfe-bc"}, + {"4.1(alsa)", "fl-fr-bl-br-lfe"}, // not in lavc + {"5.1", "fl-fr-fc-lfe-bl-br"}, + {"5.1(alsa)", "fl-fr-bl-br-fc-lfe"}, // not in lavc + {"5.1(side)", "fl-fr-fc-lfe-sl-sr"}, + {"6.0", "fl-fr-fc-bc-sl-sr"}, + {"6.0(front)", "fl-fr-flc-frc-sl-sr"}, + {"hexagonal", "fl-fr-fc-bl-br-bc"}, + {"6.1", "fl-fr-fc-lfe-bl-br-bc"}, + {"6.1(front)", "fl-fr-lfe-flc-frc-sl-sr"}, + {"7.0", "fl-fr-fc-bl-br-sl-sr"}, + {"7.0(front)", "fl-fr-fc-flc-frc-sl-sr"}, + {"7.1", "fl-fr-fc-lfe-bl-br-sl-sr"}, + {"7.1(alsa)", "fl-fr-bl-br-fc-lfe-sl-sr"}, // not in lavc + {"7.1(wide)", "fl-fr-fc-lfe-bl-br-flc-frc"}, + {"7.1(wide-side)", "fl-fr-fc-lfe-flc-frc-sl-sr"}, + {"octagonal", "fl-fr-fc-bl-br-bc-sl-sr"}, + {"downmix", "dl-dr"}, + {0} +}; + +static const struct mp_chmap default_layouts[MP_NUM_CHANNELS + 1] = { + {0}, // empty + MP_CHMAP_INIT_MONO, // mono + MP_CHMAP2(FL, FR), // stereo + MP_CHMAP3(FL, FR, LFE), // 2.1 + MP_CHMAP4(FL, FR, FC, BC), // 4.0 + MP_CHMAP5(FL, FR, FC, BL, BR), // 5.0 + MP_CHMAP6(FL, FR, FC, LFE, BL, BR), // 5.1 + MP_CHMAP7(FL, FR, FC, LFE, BL, BR, BC), // 6.1 + MP_CHMAP8(FL, FR, FC, LFE, BL, BR, SL, SR), // 7.1 +}; + +// Returns true if speakers are mapped uniquely, and there's at least 1 channel. +bool mp_chmap_is_valid(const struct mp_chmap *src) +{ + bool mapped[MP_SPEAKER_ID_COUNT] = {0}; + for (int n = 0; n < src->num; n++) { + int sp = src->speaker[n]; + if (sp >= MP_SPEAKER_ID_COUNT || mapped[sp]) + return false; + mapped[sp] = true; + } + return src->num > 0; +} + +bool mp_chmap_is_empty(const struct mp_chmap *src) +{ + return src->num == 0; +} + +// Return true if the channel map defines the number of the channels only, and +// the channels have to meaning associated with them. +bool mp_chmap_is_unknown(const struct mp_chmap *src) +{ + for (int n = 0; n < src->num; n++) { + int speaker = src->speaker[n]; + if (speaker >= MP_SPEAKER_ID_UNKNOWN0 && + speaker <= MP_SPEAKER_ID_UNKNOWN_LAST) + return true; + } + return false; +} + +// Note: empty channel maps compare as equal. Invalid ones can equal too. +bool mp_chmap_equals(const struct mp_chmap *a, const struct mp_chmap *b) +{ + if (a->num != b->num) + return false; + for (int n = 0; n < a->num; n++) { + if (a->speaker[n] != b->speaker[n]) + return false; + } + return true; +} + +// Whether they use the same speakers (even if in different order). +bool mp_chmap_equals_reordered(const struct mp_chmap *a, const struct mp_chmap *b) +{ + struct mp_chmap t1 = *a, t2 = *b; + mp_chmap_reorder_norm(&t1); + mp_chmap_reorder_norm(&t2); + return mp_chmap_equals(&t1, &t2); +} + +bool mp_chmap_is_compatible(const struct mp_chmap *a, const struct mp_chmap *b) +{ + if (mp_chmap_equals(a, b)) + return true; + if (a->num == b->num && (mp_chmap_is_unknown(a) || mp_chmap_is_unknown(b))) + return true; + return false; +} + +bool mp_chmap_is_stereo(const struct mp_chmap *src) +{ + static const struct mp_chmap stereo = MP_CHMAP_INIT_STEREO; + return mp_chmap_equals(src, &stereo); +} + +static int comp_uint8(const void *a, const void *b) +{ + return *(const uint8_t *)a - *(const uint8_t *)b; +} + +// Reorder channels to normal order, with monotonically increasing speaker IDs. +// We define this order as the same order used with waveex. +void mp_chmap_reorder_norm(struct mp_chmap *map) +{ + uint8_t *arr = &map->speaker[0]; + qsort(arr, map->num, 1, comp_uint8); +} + +// Set *dst to a standard layout with the given number of channels. +// If the number of channels is invalid, an invalid map is set, and +// mp_chmap_is_valid(dst) will return false. +void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels) +{ + if (num_channels < 0 || num_channels > MP_NUM_CHANNELS) { + *dst = (struct mp_chmap) {0}; + } else { + *dst = default_layouts[num_channels]; + } +} + +// Set *dst to an unknown layout for the given numbers of channels. +// If the number of channels is invalid, an invalid map is set, and +// mp_chmap_is_valid(dst) will return false. +void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels) +{ + if (num_channels < 0 || num_channels > MP_NUM_CHANNELS) { + *dst = (struct mp_chmap) {0}; + } else { + dst->num = num_channels; + for (int n = 0; n < dst->num; n++) + dst->speaker[n] = MP_SPEAKER_ID_UNKNOWN0 + n; + } +} + +// Return channel index of the given speaker, or -1. +static int mp_chmap_find_speaker(const struct mp_chmap *map, int speaker) +{ + for (int n = 0; n < map->num; n++) { + if (map->speaker[n] == speaker) + return n; + } + return -1; +} + +static void mp_chmap_remove_speaker(struct mp_chmap *map, int speaker) +{ + int index = mp_chmap_find_speaker(map, speaker); + if (index >= 0) { + for (int n = index; n < map->num - 1; n++) + map->speaker[n] = map->speaker[n + 1]; + map->num--; + } +} + +// Some decoders output additional, redundant channels, which are usually +// useless and will mess up proper audio output channel handling. +// map: channel map from which the channels should be removed +// requested: if not NULL, and if it contains any of the "useless" channels, +// don't remove them (this is for convenience) +void mp_chmap_remove_useless_channels(struct mp_chmap *map, + const struct mp_chmap *requested) +{ + if (requested && + mp_chmap_find_speaker(requested, MP_SPEAKER_ID_DL) >= 0) + return; + + if (map->num > 2) { + mp_chmap_remove_speaker(map, MP_SPEAKER_ID_DL); + mp_chmap_remove_speaker(map, MP_SPEAKER_ID_DR); + } +} + +// Return the ffmpeg/libav channel layout as in <libavutil/channel_layout.h>. +// Warning: this ignores the order of the channels, and will return a channel +// mask even if the order is different from libavcodec's. +uint64_t mp_chmap_to_lavc_unchecked(const struct mp_chmap *src) +{ + // lavc has no concept for unknown layouts yet, so pick a default + struct mp_chmap t = *src; + if (mp_chmap_is_unknown(&t)) + mp_chmap_from_channels(&t, t.num); + uint64_t mask = 0; + for (int n = 0; n < t.num; n++) + mask |= 1ULL << t.speaker[n]; + return mask; +} + +// Return the ffmpeg/libav channel layout as in <libavutil/channel_layout.h>. +// Returns 0 if the channel order doesn't match lavc's or if it's invalid. +uint64_t mp_chmap_to_lavc(const struct mp_chmap *src) +{ + if (!mp_chmap_is_lavc(src)) + return 0; + return mp_chmap_to_lavc_unchecked(src); +} + +// Set channel map from the ffmpeg/libav channel layout as in +// <libavutil/channel_layout.h>. +// If the number of channels exceed MP_NUM_CHANNELS, set dst to empty. +void mp_chmap_from_lavc(struct mp_chmap *dst, uint64_t src) +{ + dst->num = 0; + for (int n = 0; n < 64; n++) { + if (src & (1ULL << n)) { + if (dst->num >= MP_NUM_CHANNELS) { + dst->num = 0; + return; + } + dst->speaker[dst->num] = n; + dst->num++; + } + } +} + +bool mp_chmap_is_lavc(const struct mp_chmap *src) +{ + if (!mp_chmap_is_valid(src)) + return false; + if (mp_chmap_is_unknown(src)) + return true; + // lavc's channel layout is a bit mask, and channels are always ordered + // from LSB to MSB speaker bits, so speaker IDs have to increase. + assert(src->num > 0); + for (int n = 1; n < src->num; n++) { + if (src->speaker[n - 1] >= src->speaker[n]) + return false; + } + for (int n = 0; n < src->num; n++) { + if (src->speaker[n] >= 64) + return false; + } + return true; +} + +void mp_chmap_reorder_to_lavc(struct mp_chmap *map) +{ + if (!mp_chmap_is_valid(map)) + return; + uint64_t mask = mp_chmap_to_lavc_unchecked(map); + mp_chmap_from_lavc(map, mask); +} + +// Try to do what mplayer/mplayer2/mpv did before channel layouts were +// introduced, i.e. get the old default channel order. +void mp_chmap_reorder_to_alsa(struct mp_chmap *map) +{ + // The channel order was lavc/waveex, but differs from lavc for 5, 6 and 8 + // channels. 3 and 7 channels were likely undefined (no ALSA support). + mp_chmap_from_channels(map, map->num); + if (map->num == 5) { + mp_chmap_from_str(map, bstr0("5.0(alsa)")); + } else if (map->num == 6) { + mp_chmap_from_str(map, bstr0("5.1(alsa)")); + } else if (map->num == 8) { + mp_chmap_from_str(map, bstr0("7.1(alsa)")); + } +} + +// Get reordering array for from->to reordering. from->to must have the same set +// of speakers (i.e. same number and speaker IDs, just different order). Then, +// for each speaker n, dst[n] will be set such that: +// to->speaker[dst[n]] = from->speaker[n] +// (dst[n] gives the source channel for destination channel n) +void mp_chmap_get_reorder(int dst[MP_NUM_CHANNELS], const struct mp_chmap *from, + const struct mp_chmap *to) +{ + assert(from->num == to->num); + if (mp_chmap_is_unknown(from) || mp_chmap_is_unknown(to)) { + for (int n = 0; n < from->num; n++) + dst[n] = n; + return; + } + // Same set of speakers required + assert(mp_chmap_equals_reordered(from, to)); + for (int n = 0; n < from->num; n++) { + int src = from->speaker[n]; + dst[n] = -1; + for (int i = 0; i < to->num; i++) { + if (src == to->speaker[i]) { + dst[n] = i; + break; + } + } + assert(dst[n] != -1); + } + for (int n = 0; n < from->num; n++) + assert(to->speaker[dst[n]] == from->speaker[n]); +} + +// Returns something like "fl-fr-fc". If there's a standard layout in lavc +// order, return that, e.g. "3.0" instead of "fl-fr-fc". +// Unassigned but valid speakers get names like "sp28". +char *mp_chmap_to_str(const struct mp_chmap *src) +{ + char *res = talloc_strdup(NULL, ""); + + if (mp_chmap_is_unknown(src)) + return talloc_asprintf_append_buffer(res, "unknown%d", src->num); + + for (int n = 0; n < src->num; n++) { + int sp = src->speaker[n]; + const char *s = sp < MP_SPEAKER_ID_COUNT ? speaker_names[sp][0] : NULL; + char buf[10]; + if (!s) { + snprintf(buf, sizeof(buf), "sp%d", sp); + s = buf; + } + res = talloc_asprintf_append_buffer(res, "%s%s", n > 0 ? "-" : "", s); + } + + // To standard layout name + for (int n = 0; std_layout_names[n][0]; n++) { + if (res && strcmp(res, std_layout_names[n][1]) == 0) { + talloc_free(res); + res = talloc_strdup(NULL, std_layout_names[n][0]); + break; + } + } + + return res; +} + +// If src can be parsed as channel map (as produced by mp_chmap_to_str()), +// return true and set *dst. Otherwise, return false and don't change *dst. +// Note: call mp_chmap_is_valid() to test whether the returned map is valid +// the map could be empty, or contain multiply mapped channels +bool mp_chmap_from_str(struct mp_chmap *dst, bstr src) +{ + // Single number corresponds to mp_chmap_from_channels() + if (src.len > 0) { + bstr t = src; + bool unknown = bstr_eatstart0(&t, "unknown"); + bstr rest; + long long count = bstrtoll(t, &rest, 10); + if (rest.len == 0) { + struct mp_chmap res; + if (unknown) { + mp_chmap_set_unknown(&res, count); + } else { + mp_chmap_from_channels(&res, count); + } + if (mp_chmap_is_valid(&res)) { + *dst = res; + return true; + } + } + } + + // From standard layout name + for (int n = 0; std_layout_names[n][0]; n++) { + if (bstr_equals0(src, std_layout_names[n][0])) { + src = bstr0(std_layout_names[n][1]); + break; + } + } + + // Explicit speaker list (separated by "-") + struct mp_chmap res = {0}; + while (src.len) { + bstr s; + bstr_split_tok(src, "-", &s, &src); + int speaker = -1; + for (int n = 0; n < MP_SPEAKER_ID_COUNT; n++) { + const char *name = speaker_names[n][0]; + if (name && bstr_equals0(s, name)) { + speaker = n; + break; + } + } + if (speaker < 0) { + if (bstr_eatstart0(&s, "sp")) { + long long sp = bstrtoll(s, &s, 0); + if (s.len == 0 && sp >= 0 && sp < MP_SPEAKER_ID_COUNT) + speaker = sp; + } + if (speaker < 0) + return false; + } + if (res.num >= MP_NUM_CHANNELS) + return false; + res.speaker[res.num] = speaker; + res.num++; + } + + *dst = res; + return true; +} + +void mp_chmap_print_help(int msgt, int msgl) +{ + mp_msg(msgt, msgl, "Speakers:\n"); + for (int n = 0; n < MP_SPEAKER_ID_COUNT; n++) { + if (speaker_names[n][0]) + mp_msg(msgt, msgl, " %-16s (%s)\n", + speaker_names[n][0], speaker_names[n][1]); + } + mp_msg(msgt, msgl, "Standard layouts:\n"); + for (int n = 0; std_layout_names[n][0]; n++) { + mp_msg(msgt, msgl, " %-16s (%s)\n", + std_layout_names[n][0], std_layout_names[n][1]); + } + for (int n = 0; n < MP_NUM_CHANNELS; n++) + mp_msg(msgt, msgl, " unknown%d\n", n); +} diff --git a/audio/chmap.h b/audio/chmap.h new file mode 100644 index 0000000000..9ab97ac8ba --- /dev/null +++ b/audio/chmap.h @@ -0,0 +1,133 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MP_CHMAP_H +#define MP_CHMAP_H + +#include <inttypes.h> +#include <stdbool.h> +#include "core/bstr.h" + +#define MP_NUM_CHANNELS 8 + +// Speaker a channel can be assigned to. +// This corresponds to WAVEFORMATEXTENSIBLE channel mask bit indexes. +// E.g. channel_mask = (1 << MP_SPEAKER_ID_FL) | ... +enum { + // Official WAVEFORMATEXTENSIBLE (shortened names) + MP_SPEAKER_ID_FL = 0, // FRONT_LEFT + MP_SPEAKER_ID_FR, // FRONT_RIGHT + MP_SPEAKER_ID_FC, // FRONT_CENTER + MP_SPEAKER_ID_LFE, // LOW_FREQUENCY + MP_SPEAKER_ID_BL, // BACK_LEFT + MP_SPEAKER_ID_BR, // BACK_RIGHT + MP_SPEAKER_ID_FLC, // FRONT_LEFT_OF_CENTER + MP_SPEAKER_ID_FRC, // FRONT_RIGHT_OF_CENTER + MP_SPEAKER_ID_BC, // BACK_CENTER + MP_SPEAKER_ID_SL, // SIDE_LEFT + MP_SPEAKER_ID_SR, // SIDE_RIGHT + MP_SPEAKER_ID_TC, // TOP_CENTER + MP_SPEAKER_ID_TFL, // TOP_FRONT_LEFT + MP_SPEAKER_ID_TFC, // TOP_FRONT_CENTER + MP_SPEAKER_ID_TFR, // TOP_FRONT_RIGHT + MP_SPEAKER_ID_TBL, // TOP_BACK_LEFT + MP_SPEAKER_ID_TBC, // TOP_BACK_CENTER + MP_SPEAKER_ID_TBR, // TOP_BACK_RIGHT + // Inofficial/libav* extensions + MP_SPEAKER_ID_DL = 29, // STEREO_LEFT (stereo downmix special speakers) + MP_SPEAKER_ID_DR, // STEREO_RIGHT + MP_SPEAKER_ID_WL, // WIDE_LEFT + MP_SPEAKER_ID_WR, // WIDE_RIGHT + MP_SPEAKER_ID_SDL, // SURROUND_DIRECT_LEFT + MP_SPEAKER_ID_SDR, // SURROUND_DIRECT_RIGHT + MP_SPEAKER_ID_LFE2, // LOW_FREQUENCY_2 + + // Special mpv-specific speaker entries reserved for channels which have no + // known meaning. + MP_SPEAKER_ID_UNKNOWN0 = 64, + MP_SPEAKER_ID_UNKNOWN_LAST = MP_SPEAKER_ID_UNKNOWN0 + MP_NUM_CHANNELS - 1, + + // Including the unassigned IDs in between. This is not a valid ID anymore. + MP_SPEAKER_ID_COUNT, +}; + +struct mp_chmap { + uint8_t num; // number of channels + // Given a channel n, speaker[n] is the speaker ID driven by that channel. + // Entries after speaker[num - 1] are undefined. + uint8_t speaker[MP_NUM_CHANNELS]; +}; + +#define MP_SP(speaker) MP_SPEAKER_ID_ ## speaker + +#define MP_CHMAP2(a, b) \ + {2, {MP_SP(a), MP_SP(b)}} +#define MP_CHMAP3(a, b, c) \ + {3, {MP_SP(a), MP_SP(b), MP_SP(c)}} +#define MP_CHMAP4(a, b, c, d) \ + {4, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d)}} +#define MP_CHMAP5(a, b, c, d, e) \ + {5, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e)}} +#define MP_CHMAP6(a, b, c, d, e, f) \ + {6, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f)}} +#define MP_CHMAP7(a, b, c, d, e, f, g) \ + {7, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g)}} +#define MP_CHMAP8(a, b, c, d, e, f, g, h) \ + {8, {MP_SP(a), MP_SP(b), MP_SP(c), MP_SP(d), MP_SP(e), MP_SP(f), MP_SP(g), MP_SP(h)}} + +#define MP_CHMAP_INIT_MONO {1, {MP_SPEAKER_ID_FC}} +#define MP_CHMAP_INIT_STEREO MP_CHMAP2(FL, FR) + +bool mp_chmap_is_valid(const struct mp_chmap *src); +bool mp_chmap_is_empty(const struct mp_chmap *src); +bool mp_chmap_is_unknown(const struct mp_chmap *src); +bool mp_chmap_equals(const struct mp_chmap *a, const struct mp_chmap *b); +bool mp_chmap_equals_reordered(const struct mp_chmap *a, const struct mp_chmap *b); +bool mp_chmap_is_compatible(const struct mp_chmap *a, const struct mp_chmap *b); +bool mp_chmap_is_stereo(const struct mp_chmap *src); + +void mp_chmap_reorder_norm(struct mp_chmap *map); + +void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels); +void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels); + +void mp_chmap_remove_useless_channels(struct mp_chmap *map, + const struct mp_chmap *requested); + +uint64_t mp_chmap_to_lavc(const struct mp_chmap *src); +uint64_t mp_chmap_to_lavc_unchecked(const struct mp_chmap *src); +void mp_chmap_from_lavc(struct mp_chmap *dst, uint64_t src); + +bool mp_chmap_is_lavc(const struct mp_chmap *src); +void mp_chmap_reorder_to_lavc(struct mp_chmap *map); + +void mp_chmap_reorder_to_alsa(struct mp_chmap *map); + +void mp_chmap_get_reorder(int dst[MP_NUM_CHANNELS], const struct mp_chmap *from, + const struct mp_chmap *to); + +char *mp_chmap_to_str(const struct mp_chmap *src); +bool mp_chmap_from_str(struct mp_chmap *dst, bstr src); +void mp_chmap_print_help(int msgt, int msgl); + +// Use these to avoid chaos in case lavc's definition should diverge from MS. +#define mp_chmap_to_waveext mp_chmap_to_lavc +#define mp_chmap_from_waveext mp_chmap_from_lavc +#define mp_chmap_is_waveext mp_chmap_is_lavc +#define mp_chmap_reorder_to_waveext mp_chmap_reorder_to_lavc + +#endif diff --git a/audio/filter/af.h b/audio/filter/af.h index 623509c9df..96e5c66607 100644 --- a/audio/filter/af.h +++ b/audio/filter/af.h @@ -26,15 +26,14 @@ #include "core/options.h" #include "audio/format.h" +#include "audio/chmap.h" #include "control.h" #include "core/mp_msg.h" struct af_instance; // Number of channels -#ifndef AF_NCH -#define AF_NCH 8 -#endif +#define AF_NCH MP_NUM_CHANNELS // Audio data chunk struct mp_audio { diff --git a/core/m_option.c b/core/m_option.c index 6e7b20a688..b1e0f50dd9 100644 --- a/core/m_option.c +++ b/core/m_option.c @@ -1555,6 +1555,48 @@ const m_option_type_t m_option_type_afmt = { .copy = copy_opt, }; +#include "audio/chmap.h" + +static int parse_chmap(const m_option_t *opt, struct bstr name, + struct bstr param, void *dst) +{ + // min>0: at least min channels, min=0: empty ok, min=-1: invalid ok + int min_ch = (opt->flags & M_OPT_MIN) ? opt->min : 1; + + if (bstr_equals0(param, "help")) { + mp_chmap_print_help(MSGT_CFGPARSER, MSGL_INFO); + return M_OPT_EXIT - 1; + } + + if (param.len == 0 && min_ch >= 1) + return M_OPT_MISSING_PARAM; + + struct mp_chmap res = {0}; + if (!mp_chmap_from_str(&res, param)) { + mp_msg(MSGT_CFGPARSER, MSGL_ERR, + "Error parsing channel layout: %.*s\n", BSTR_P(param)); + return M_OPT_INVALID; + } + + if ((min_ch > 0 && !mp_chmap_is_valid(&res)) || + (min_ch >= 0 && mp_chmap_is_empty(&res))) + { + mp_msg(MSGT_CFGPARSER, MSGL_ERR, + "Invalid channel layout: %.*s\n", BSTR_P(param)); + return M_OPT_INVALID; + } + + *(struct mp_chmap *)dst = res; + + return 1; +} + +const m_option_type_t m_option_type_chmap = { + .name = "Audio channels or channel map", + .size = sizeof(struct mp_chmap *), + .parse = parse_chmap, + .copy = copy_opt, +}; static int parse_timestring(struct bstr str, double *time, char endchar) { diff --git a/core/m_option.h b/core/m_option.h index fd0ebbd44c..66e7208801 100644 --- a/core/m_option.h +++ b/core/m_option.h @@ -60,6 +60,7 @@ extern const m_option_type_t m_option_type_afmt; extern const m_option_type_t m_option_type_color; extern const m_option_type_t m_option_type_geometry; extern const m_option_type_t m_option_type_size_box; +extern const m_option_type_t m_option_type_chmap; // Callback used by m_option_type_print_func options. typedef int (*m_opt_func_full_t)(const m_option_t *, const char *, const char *); @@ -581,6 +582,9 @@ static inline void m_option_free(const m_option_t *opt, void *dst) #define OPT_AUDIOFORMAT(...) \ OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_afmt) +#define OPT_CHMAP(...) \ + OPT_GENERAL(struct mp_chmap, __VA_ARGS__, .type = &m_option_type_chmap) + #define M_CHOICES(choices) \ .priv = (void *)&(const struct m_opt_choice_alternatives[]){ \ |