diff --git a/Makefile b/Makefile index 160d180188..34d79d638b 100644 --- a/Makefile +++ b/Makefile @@ -124,6 +124,7 @@ endif SOURCES = talloc.c \ audio/audio.c \ audio/chmap.c \ + audio/chmap_sel.c \ audio/fmt-conversion.c \ audio/format.c \ audio/mixer.c \ diff --git a/audio/audio.c b/audio/audio.c index 8af6a20a1f..c9d5c9231c 100644 --- a/audio/audio.c +++ b/audio/audio.c @@ -37,8 +37,7 @@ void mp_audio_set_num_channels(struct mp_audio *mpa, int num_channels) void mp_audio_set_channels_old(struct mp_audio *mpa, int num_channels) { struct mp_chmap map; - mp_chmap_from_channels(&map, num_channels); - mp_chmap_reorder_to_alsa(&map); + mp_chmap_from_channels_alsa(&map, num_channels); mp_audio_set_channels(mpa, &map); } diff --git a/audio/chmap.c b/audio/chmap.c index 61df408e02..fcdb95edb3 100644 --- a/audio/chmap.c +++ b/audio/chmap.c @@ -100,6 +100,17 @@ static const struct mp_chmap default_layouts[MP_NUM_CHANNELS + 1] = { MP_CHMAP8(FL, FR, FC, LFE, BL, BR, SL, SR), // 7.1 }; +// 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). +static const char *mplayer_layouts[MP_NUM_CHANNELS + 1] = { + [1] = "mono", + [2] = "stereo", + [4] = "4.0", + [5] = "5.0(alsa)", + [6] = "5.1(alsa)", + [8] = "7.1(alsa)", +}; + // Returns true if speakers are mapped uniquely, and there's at least 1 channel. bool mp_chmap_is_valid(const struct mp_chmap *src) { @@ -192,6 +203,19 @@ void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels) } } +// Try to do what mplayer/mplayer2/mpv did before channel layouts were +// introduced, i.e. get the old default channel order. +void mp_chmap_from_channels_alsa(struct mp_chmap *dst, int num_channels) +{ + if (num_channels < 0 || num_channels > MP_NUM_CHANNELS) { + *dst = (struct mp_chmap) {0}; + } else { + mp_chmap_from_str(dst, bstr0(mplayer_layouts[num_channels])); + if (!dst->num) + mp_chmap_from_channels(dst, 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. @@ -314,22 +338,6 @@ void mp_chmap_reorder_to_lavc(struct mp_chmap *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: diff --git a/audio/chmap.h b/audio/chmap.h index 9ab97ac8ba..929283dfd5 100644 --- a/audio/chmap.h +++ b/audio/chmap.h @@ -104,6 +104,7 @@ 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_from_channels_alsa(struct mp_chmap *dst, int num_channels); void mp_chmap_remove_useless_channels(struct mp_chmap *map, const struct mp_chmap *requested); @@ -115,8 +116,6 @@ 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); @@ -130,4 +129,6 @@ void mp_chmap_print_help(int msgt, int msgl); #define mp_chmap_is_waveext mp_chmap_is_lavc #define mp_chmap_reorder_to_waveext mp_chmap_reorder_to_lavc +#define mp_chmap_reorder_to_alsa(x) mp_chmap_from_channels_alsa((x), (x)->num) + #endif diff --git a/audio/chmap_sel.c b/audio/chmap_sel.c new file mode 100644 index 0000000000..8e5be5c86e --- /dev/null +++ b/audio/chmap_sel.c @@ -0,0 +1,210 @@ +/* + * 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 . + */ + +#include +#include + +#include "chmap_sel.h" + +// 5.1 and 5.1(side) are practically the same. It doesn't make much sense to +// reject either of them. +static const int replaceable_speakers[][2] = { + {MP_SPEAKER_ID_SL, MP_SPEAKER_ID_BL}, + {MP_SPEAKER_ID_SR, MP_SPEAKER_ID_BR}, + {-1}, +}; + +// list[] contains a list of speaker pairs, with each pair indicating how +// a speaker can be swapped for another speaker. Try to replace speakers from +// the left of the list with the ones on the right, or the other way around. +static bool replace_speakers(struct mp_chmap *map, const int list[][2]) +{ + if (!mp_chmap_is_valid(map)) + return false; + for (int dir = 0; dir < 2; dir++) { + int from = dir ? 0 : 1; + int to = dir ? 1 : 0; + bool replaced = false; + struct mp_chmap t = *map; + for (int n = 0; n < t.num; n++) { + for (int i = 0; list[i][0] != -1; i++) { + if (t.speaker[n] == list[i][from]) { + t.speaker[n] = list[i][to]; + replaced = true; + break; + } + } + } + if (replaced && mp_chmap_is_valid(&t)) { + *map = t; + return true; + } + } + return false; +} + +// Allow all channel layouts that can be expressed with mp_chmap. +// (By default, all layouts are rejected.) +void mp_chmap_sel_add_any(struct mp_chmap_sel *s) +{ + s->allow_any = true; +} + +// Allow all waveext formats, and force waveext channel order. +void mp_chmap_sel_add_waveext(struct mp_chmap_sel *s) +{ + s->allow_waveext = true; +} + +void mp_chmap_sel_add_alsa_def(struct mp_chmap_sel *s) +{ + for (int n = 0; n < MP_NUM_CHANNELS; n++) { + struct mp_chmap t; + mp_chmap_from_channels_alsa(&t, n); + if (t.num) + mp_chmap_sel_add_map(s, &t); + } +} + +#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0])) + +// Add a channel map that should be allowed. +void mp_chmap_sel_add_map(struct mp_chmap_sel *s, const struct mp_chmap *map) +{ + assert(s->num_chmaps < ARRAY_LEN(s->chmaps)); + if (mp_chmap_is_valid(map)) + s->chmaps[s->num_chmaps++] = *map; +} + +// Allow all waveext formats in default order. +void mp_chmap_sel_add_waveext_def(struct mp_chmap_sel *s) +{ + for (int n = 1; n < MP_NUM_CHANNELS; n++) { + struct mp_chmap map; + mp_chmap_from_channels(&map, n); + mp_chmap_sel_add_map(s, &map); + } +} + +// Whitelist a speaker (MP_SPEAKER_ID_...). All layouts that contain whitelisted +// speakers are allowed. +void mp_chmap_sel_add_speaker(struct mp_chmap_sel *s, int id) +{ + assert(id >= 0 && id < MP_SPEAKER_ID_COUNT); + s->speakers[id] = true; +} + +static bool test_speakers(const struct mp_chmap_sel *s, struct mp_chmap *map) +{ + for (int n = 0; n < map->num; n++) { + if (!s->speakers[map->speaker[n]]) + return false; + } + return true; +} + +static bool test_maps(const struct mp_chmap_sel *s, struct mp_chmap *map) +{ + for (int n = 0; n < s->num_chmaps; n++) { + if (mp_chmap_equals_reordered(&s->chmaps[n], map)) { + *map = s->chmaps[n]; + return true; + } + } + return false; +} + +static bool test_waveext(const struct mp_chmap_sel *s, struct mp_chmap *map) +{ + if (s->allow_waveext) { + struct mp_chmap t = *map; + mp_chmap_reorder_to_waveext(&t); + if (mp_chmap_is_waveext(&t)) { + *map = t; + return true; + } + } + return false; +} + +static bool test_layout(const struct mp_chmap_sel *s, struct mp_chmap *map) +{ + if (!mp_chmap_is_valid(map)) + return false; + + return s->allow_any || test_waveext(s, map) || test_speakers(s, map) || + test_maps(s, map); +} + +// Determine which channel map to use given a source channel map, and various +// parameters restricting possible choices. If the map doesn't match, select +// a fallback and set it. +// If no matching layout is found, a reordered layout may be returned. +// If that is not possible, a fallback for up/downmixing may be returned. +// If no choice is possible, set *map to empty. +bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map) +{ + if (test_layout(s, map)) + return true; + if (mp_chmap_is_unknown(map)) { + struct mp_chmap t = {0}; + if (mp_chmap_sel_get_def(s, &t, map->num) && test_layout(s, &t)) { + *map = t; + return true; + } + } + // 5.1 <-> 5.1(side) + if (replace_speakers(map, replaceable_speakers) && test_layout(s, map)) + return true; + // Fallback to mono/stereo as last resort + if (map->num == 1) { + *map = (struct mp_chmap) MP_CHMAP_INIT_MONO; + } else if (map->num >= 2) { + *map = (struct mp_chmap) MP_CHMAP_INIT_STEREO; + } + if (test_layout(s, map)) + return true; + *map = (struct mp_chmap) {0}; + return false; +} + +// Set map to a default layout with num channels. Used for audio APIs that +// return a channel count as part of format negotiation, but give no +// information about the channel layout. +// If the channel count is correct, do nothing and leave *map untouched. +bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map, + int num) +{ + if (map->num != num) { + *map = (struct mp_chmap) {0}; + // Set of speakers or waveext might allow it. + struct mp_chmap t; + mp_chmap_from_channels(&t, num); + mp_chmap_reorder_to_waveext(&t); + if (test_layout(s, &t)) { + *map = t; + } else { + for (int n = 0; n < s->num_chmaps; n++) { + if (s->chmaps[n].num == num) { + *map = s->chmaps[n]; + break; + } + } + } + } + return map->num > 0; +} diff --git a/audio/chmap_sel.h b/audio/chmap_sel.h new file mode 100644 index 0000000000..c9d75196a5 --- /dev/null +++ b/audio/chmap_sel.h @@ -0,0 +1,43 @@ +/* + * 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 . + */ + +#ifndef MP_CHMAP_SEL_H +#define MP_CHMAP_SEL_H + +#include + +#include "chmap.h" + +struct mp_chmap_sel { + // should be considered opaque + bool allow_any, allow_waveext; + bool speakers[MP_SPEAKER_ID_COUNT]; + struct mp_chmap chmaps[20]; + int num_chmaps; +}; + +void mp_chmap_sel_add_any(struct mp_chmap_sel *s); +void mp_chmap_sel_add_waveext(struct mp_chmap_sel *s); +void mp_chmap_sel_add_waveext_def(struct mp_chmap_sel *s); +void mp_chmap_sel_add_alsa_def(struct mp_chmap_sel *s); +void mp_chmap_sel_add_map(struct mp_chmap_sel *s, const struct mp_chmap *map); +void mp_chmap_sel_add_speaker(struct mp_chmap_sel *s, int id); +bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map); +bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map, + int num); + +#endif diff --git a/audio/out/ao.c b/audio/out/ao.c index bf9b47a14e..10badcfa07 100644 --- a/audio/out/ao.c +++ b/audio/out/ao.c @@ -250,7 +250,17 @@ void ao_resume(struct ao *ao) ao->driver->resume(ao); } +bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s, + struct mp_chmap *map) +{ + return mp_chmap_sel_adjust(s, map); +} +bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s, + struct mp_chmap *map, int num) +{ + return mp_chmap_sel_get_def(s, map, num); +} int old_ao_init(struct ao *ao, char *params) { diff --git a/audio/out/ao.h b/audio/out/ao.h index 50f121bec2..d908841457 100644 --- a/audio/out/ao.h +++ b/audio/out/ao.h @@ -24,6 +24,7 @@ #include "core/bstr.h" #include "core/mp_common.h" #include "audio/chmap.h" +#include "audio/chmap_sel.h" enum aocontrol { // _VOLUME commands take struct ao_control_vol pointer for input/output. @@ -121,6 +122,11 @@ void ao_reset(struct ao *ao); void ao_pause(struct ao *ao); void ao_resume(struct ao *ao); +bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s, + struct mp_chmap *map); +bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s, + struct mp_chmap *map, int num); + int old_ao_control(struct ao *ao, enum aocontrol cmd, void *arg); int old_ao_init(struct ao *ao, char *params); void old_ao_uninit(struct ao *ao, bool cut_audio);