summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/options.rst63
-rw-r--r--audio/chmap_sel.c13
-rw-r--r--audio/chmap_sel.h2
-rw-r--r--audio/decode/ad_lavc.c4
-rw-r--r--audio/filter/af_format.c16
-rw-r--r--audio/out/ao.c34
-rw-r--r--audio/out/ao.h10
-rw-r--r--audio/out/ao_alsa.c2
-rw-r--r--audio/out/ao_lavc.c2
-rw-r--r--audio/out/internal.h3
-rw-r--r--demux/demux_raw.c17
-rw-r--r--options/m_option.c104
-rw-r--r--options/m_option.h18
-rw-r--r--options/options.c3
-rw-r--r--options/options.h2
-rw-r--r--player/audio.c18
-rw-r--r--player/main.c5
17 files changed, 234 insertions, 82 deletions
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 6497d14684..456c3c3f94 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -1150,30 +1150,51 @@ Audio
This and enabling passthrough via ``--ad`` are deprecated in favor of
using ``--audio-spdif=dts-hd``.
-``--audio-channels=<auto|number|layout>``
- Request a channel layout for audio output (default: stereo). This will ask
- the AO to open a device with the given channel layout. It's up to the AO
- to accept this layout, or to pick a fallback or to error out if the
- requested layout is not supported.
-
- The ``--audio-channels`` option either takes a channel number or an explicit
- channel layout. Channel numbers refer to default layouts, e.g. 2 channels
- refer to stereo, 6 refers to 5.1.
+``--audio-channels=<auto-safe|auto|layouts>``
+ Control which audio channels are output (e.g. surround vs. stereo). There
+ are the following possibilities:
+
+ - ``--audio-channels=auto-safe``
+ Use the system's preferred channel layout. If there is none (such
+ as when accessing a hardware device instead of the system mixer),
+ force stereo. Some audio outputs might simply accept any layout and
+ do downmixing on their own.
+
+ This is the default.
+ - ``--audio-channels=auto``
+ Send the audio device whatever it accepts, preferring the audio's
+ original channel layout. Can cause issues with HDMI (see the warning
+ below).
+ - ``--audio-channels=layout1,layout2,...``
+ List of ``,``-separated channel layouts which should be allowed.
+ Technically, this only adjusts the filter chain output to the best
+ matching layout in the list, and passes the result to the audio API.
+ It's possible that the audio API will select a different channel
+ layout.
+
+ Using this mode is recommended for direct hardware output, especially
+ over HDMI (see HDMI warning below).
+ - ``--audio-channels=stereo``
+ Force a plain stereo downmix. This is a special-case of the previous
+ item. (See paragraphs below for implications.)
+
+ If a list of layouts is given, each item can be either an explicit channel
+ layout name (like ``5.1``), or a channel number. Channel numbers refer to
+ default layouts, e.g. 2 channels refer to stereo, 6 refers to 5.1.
See ``--audio-channels=help`` output for defined default layouts. This also
lists speaker names, which can be used to express arbitrary channel
layouts (e.g. ``fl-fr-lfe`` is 2.1).
- ``--audio-channels=auto`` tries to play audio using the input file's
- channel layout. There is no guarantee that the audio API handles this
- correctly. See the HDMI warning below.
- (``empty`` is an accepted obsolete alias for ``auto``.)
-
- This will also request the channel layout from the decoder. If the decoder
- does not support the layout, it will fall back to its native channel layout.
- (You can use ``--ad-lavc-downmix=no`` to make the decoder always output
- its native layout.) Note that only some decoders support remixing audio.
- Some that do include AC-3, AAC or DTS audio.
+ If the list of channel layouts has only 1 item, the decoder is asked to
+ produce according output. This sometimes triggers decoder-downmix, which
+ might be different from the normal mpv downmix. (Only some decoders support
+ remixing audio, like AC-3, AAC or DTS. You can use ``--ad-lavc-downmix=no``
+ to make the decoder always output its native layout.) One consequence is
+ that ``--audio-channels=stereo`` triggers decoder downmix, while ``auto``
+ or ``auto-safe`` never will, even if they end up selecting stereo. This
+ happens because the decision whether to use decoder downmix happens long
+ before the audio device is opened.
If the channel layout of the media file (i.e. the decoder) and the AO's
channel layout don't match, mpv will attempt to insert a conversion filter.
@@ -1186,6 +1207,10 @@ Audio
channel layout, random things can happen, such as dropping the
additional channels, or adding noise.
+ You are recommended to set an explicit whitelist of the layouts you
+ want. For example, most A/V receivers connected via HDMI and that can
+ do 7.1 would be served by: ``--audio-channels=7.1,5.1,stereo``
+
``--audio-normalize-downmix=<yes|no>``
Enable/disable normalization if surround audio is downmixed to stereo
(default: no). If this is disabled, downmix can cause clipping. If it's
diff --git a/audio/chmap_sel.c b/audio/chmap_sel.c
index 45b696c924..4fb7544f20 100644
--- a/audio/chmap_sel.c
+++ b/audio/chmap_sel.c
@@ -374,3 +374,16 @@ void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev)
if (s->allow_any)
mp_msg(log, lev, " - anything\n");
}
+
+// Select a channel map from the given list that fits best to c. Don't change
+// *c if there's no match, or the list is empty.
+void mp_chmap_sel_list(struct mp_chmap *c, struct mp_chmap *maps, int num_maps)
+{
+ // This is a separate function to keep messing with mp_chmap_sel internals
+ // within this source file.
+ struct mp_chmap_sel sel = {
+ .chmaps = maps,
+ .num_chmaps = num_maps,
+ };
+ mp_chmap_sel_fallback(&sel, c);
+}
diff --git a/audio/chmap_sel.h b/audio/chmap_sel.h
index 5bd8783b83..4b11557a2b 100644
--- a/audio/chmap_sel.h
+++ b/audio/chmap_sel.h
@@ -47,4 +47,6 @@ bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map,
struct mp_log;
void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev);
+void mp_chmap_sel_list(struct mp_chmap *c, struct mp_chmap *maps, int num_maps);
+
#endif
diff --git a/audio/decode/ad_lavc.c b/audio/decode/ad_lavc.c
index 0316f6b7d1..c785c62c90 100644
--- a/audio/decode/ad_lavc.c
+++ b/audio/decode/ad_lavc.c
@@ -104,9 +104,9 @@ static int init(struct dec_audio *da, const char *decoder)
lavc_context->codec_type = AVMEDIA_TYPE_AUDIO;
lavc_context->codec_id = lavc_codec->id;
- if (opts->downmix) {
+ if (opts->downmix && mpopts->audio_output_channels.num_chmaps == 1) {
lavc_context->request_channel_layout =
- mp_chmap_to_lavc(&mpopts->audio_output_channels);
+ mp_chmap_to_lavc(&mpopts->audio_output_channels.chmaps[0]);
}
// Always try to set - option only exists for AC3 at the moment
diff --git a/audio/filter/af_format.c b/audio/filter/af_format.c
index c0fe354a39..748c5cbd52 100644
--- a/audio/filter/af_format.c
+++ b/audio/filter/af_format.c
@@ -29,10 +29,10 @@ struct priv {
int in_format;
int in_srate;
- struct mp_chmap in_channels;
+ struct m_channels in_channels;
int out_format;
int out_srate;
- struct mp_chmap out_channels;
+ struct m_channels out_channels;
int fail;
};
@@ -44,8 +44,8 @@ static void force_in_params(struct af_instance *af, struct mp_audio *in)
if (priv->in_format != AF_FORMAT_UNKNOWN)
mp_audio_set_format(in, priv->in_format);
- if (priv->in_channels.num)
- mp_audio_set_channels(in, &priv->in_channels);
+ if (priv->in_channels.num_chmaps > 0)
+ mp_audio_set_channels(in, &priv->in_channels.chmaps[0]);
if (priv->in_srate)
in->rate = priv->in_srate;
@@ -58,8 +58,8 @@ static void force_out_params(struct af_instance *af, struct mp_audio *out)
if (priv->out_format != AF_FORMAT_UNKNOWN)
mp_audio_set_format(out, priv->out_format);
- if (priv->out_channels.num)
- mp_audio_set_channels(out, &priv->out_channels);
+ if (priv->out_channels.num_chmaps > 0)
+ mp_audio_set_channels(out, &priv->out_channels.chmaps[0]);
if (priv->out_srate)
out->rate = priv->out_srate;
@@ -124,10 +124,10 @@ const struct af_info af_info_format = {
.options = (const struct m_option[]) {
OPT_AUDIOFORMAT("format", in_format, 0),
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
- OPT_CHMAP("channels", in_channels, CONF_MIN, .min = 0),
+ OPT_CHANNELS("channels", in_channels, 0, .min = 1),
OPT_AUDIOFORMAT("out-format", out_format, 0),
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
- OPT_CHMAP("out-channels", out_channels, CONF_MIN, .min = 0),
+ OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
OPT_FLAG("fail", fail, 0),
{0}
},
diff --git a/audio/out/ao.c b/audio/out/ao.c
index c9d8f42b4a..0647067e50 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -158,7 +158,7 @@ error:
static struct ao *ao_init(bool probing, struct mpv_global *global,
struct input_ctx *input_ctx,
- struct encode_lavc_context *encode_lavc_ctx,
+ struct encode_lavc_context *encode_lavc_ctx, int flags,
int samplerate, int format, struct mp_chmap channels,
char *dev, char *name, char **args)
{
@@ -169,6 +169,7 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
ao->channels = channels;
ao->format = format;
ao->encode_lavc_ctx = encode_lavc_ctx;
+ ao->init_flags = flags;
if (ao->driver->encode != !!ao->encode_lavc_ctx)
goto fail;
@@ -190,7 +191,7 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
snprintf(redirect, sizeof(redirect), "%s", ao->redirect);
snprintf(rdevice, sizeof(rdevice), "%s", ao->device ? ao->device : "");
talloc_free(ao);
- return ao_init(probing, global, input_ctx, encode_lavc_ctx,
+ return ao_init(probing, global, input_ctx, encode_lavc_ctx, flags,
samplerate, format, channels, rdevice, redirect, NULL);
}
goto fail;
@@ -240,7 +241,7 @@ static void split_ao_device(void *tmp, char *opt, char **out_ao, char **out_dev)
}
struct ao *ao_init_best(struct mpv_global *global,
- bool ao_null_fallback,
+ int init_flags,
struct input_ctx *input_ctx,
struct encode_lavc_context *encode_lavc_ctx,
int samplerate, int format, struct mp_chmap channels)
@@ -283,7 +284,7 @@ struct ao *ao_init_best(struct mpv_global *global,
}
}
- if (ao_null_fallback) {
+ if (init_flags & AO_INIT_NULL_FALLBACK) {
MP_TARRAY_APPEND(tmp, ao_list, ao_num,
(struct m_obj_settings){.name = "null"});
}
@@ -297,7 +298,7 @@ struct ao *ao_init_best(struct mpv_global *global,
dev = pref_dev;
mp_verbose(log, "Using preferred device '%s'\n", dev);
}
- ao = ao_init(probing, global, input_ctx, encode_lavc_ctx,
+ ao = ao_init(probing, global, input_ctx, encode_lavc_ctx, init_flags,
samplerate, format, channels, dev,
entry->name, entry->attribs);
if (ao)
@@ -429,6 +430,29 @@ bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
return r;
}
+// safe_multichannel=true behaves like ao_chmap_sel_adjust.
+// safe_multichannel=false is a helper for callers which do not support safe
+// handling of arbitrary channel layouts. If the multichannel layouts are not
+// considered "always safe" (e.g. HDMI), then allow only stereo or mono, if
+// they are part of the list in *s.
+bool ao_chmap_sel_adjust2(struct ao *ao, const struct mp_chmap_sel *s,
+ struct mp_chmap *map, bool safe_multichannel)
+{
+ if (!safe_multichannel && (ao->init_flags & AO_INIT_SAFE_MULTICHANNEL_ONLY)) {
+ struct mp_chmap res = *map;
+ if (mp_chmap_sel_adjust(s, &res)) {
+ if (!mp_chmap_equals(&res, &(struct mp_chmap)MP_CHMAP_INIT_MONO) &&
+ !mp_chmap_equals(&res, &(struct mp_chmap)MP_CHMAP_INIT_STEREO))
+ {
+ MP_WARN(ao, "Disabling multichannel output.\n");
+ *map = (struct mp_chmap)MP_CHMAP_INIT_STEREO;
+ }
+ }
+ }
+
+ return ao_chmap_sel_adjust(ao, s, map);
+}
+
bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
struct mp_chmap *map, int num)
{
diff --git a/audio/out/ao.h b/audio/out/ao.h
index e8e64e33eb..3c16cef0e5 100644
--- a/audio/out/ao.h
+++ b/audio/out/ao.h
@@ -50,6 +50,14 @@ enum {
AO_EVENT_HOTPLUG = 2,
};
+enum {
+ // Allow falling back to ao_null if nothing else works.
+ AO_INIT_NULL_FALLBACK = 1 << 0,
+ // Only accept multichannel configurations that are guaranteed to work
+ // (i.e. not sending arbitrary layouts over HDMI).
+ AO_INIT_SAFE_MULTICHANNEL_ONLY = 1 << 1,
+};
+
typedef struct ao_control_vol {
float left;
float right;
@@ -72,7 +80,7 @@ struct encode_lavc_context;
struct mp_audio;
struct ao *ao_init_best(struct mpv_global *global,
- bool ao_null_fallback,
+ int init_flags,
struct input_ctx *input_ctx,
struct encode_lavc_context *encode_lavc_ctx,
int samplerate, int format, struct mp_chmap channels);
diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c
index d09f5fc499..4a55d19656 100644
--- a/audio/out/ao_alsa.c
+++ b/audio/out/ao_alsa.c
@@ -360,7 +360,7 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
snd_pcm_free_chmaps(maps);
- return ao_chmap_sel_adjust(ao, &chmap_sel, chmap);
+ return ao_chmap_sel_adjust2(ao, &chmap_sel, chmap, false);
}
// Map back our selected channel layout to an ALSA one. This is done this way so
diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c
index 6b4279ca87..8ae1317407 100644
--- a/audio/out/ao_lavc.c
+++ b/audio/out/ao_lavc.c
@@ -123,7 +123,7 @@ static int init(struct ao *ao)
struct mp_chmap_sel sel = {0};
mp_chmap_sel_add_any(&sel);
- if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
+ if (!ao_chmap_sel_adjust2(ao, &sel, &ao->channels, false))
goto fail;
mp_chmap_reorder_to_lavc(&ao->channels);
ac->codec->channels = ao->channels.num;
diff --git a/audio/out/internal.h b/audio/out/internal.h
index 49131ba293..88160bbeb6 100644
--- a/audio/out/internal.h
+++ b/audio/out/internal.h
@@ -43,6 +43,7 @@ struct ao {
struct encode_lavc_context *encode_lavc_ctx;
struct input_ctx *input_ctx;
struct mp_log *log; // Using e.g. "[ao/coreaudio]" as prefix
+ int init_flags; // AO_INIT_* flags
// The device as selected by the user, usually using ao_device_desc.name
// from an entry from the list returned by driver->list_devices. If the
@@ -191,6 +192,8 @@ void ao_wakeup_poll(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_adjust2(struct ao *ao, const struct mp_chmap_sel *s,
+ struct mp_chmap *map, bool safe_multichannel);
bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
struct mp_chmap *map, int num);
diff --git a/demux/demux_raw.c b/demux/demux_raw.c
index e4cf596768..bd8e11306d 100644
--- a/demux/demux_raw.c
+++ b/demux/demux_raw.c
@@ -36,7 +36,7 @@
#include "osdep/endian.h"
struct demux_rawaudio_opts {
- struct mp_chmap channels;
+ struct m_channels channels;
int samplerate;
int aformat;
};
@@ -49,7 +49,7 @@ struct demux_rawaudio_opts {
#define OPT_BASE_STRUCT struct demux_rawaudio_opts
const struct m_sub_options demux_rawaudio_conf = {
.opts = (const m_option_t[]) {
- OPT_CHMAP("channels", channels, CONF_MIN, .min = 1),
+ OPT_CHANNELS("channels", channels, 0, .min = 1),
OPT_INTRANGE("rate", samplerate, 0, 1000, 8 * 48000),
OPT_CHOICE("format", aformat, 0,
({"u8", PCM(0, 0, 8, 0)},
@@ -75,7 +75,11 @@ const struct m_sub_options demux_rawaudio_conf = {
.size = sizeof(struct demux_rawaudio_opts),
.defaults = &(const struct demux_rawaudio_opts){
// Note that currently, stream_cdda expects exactly these parameters!
- .channels = MP_CHMAP_INIT_STEREO,
+ .channels = {
+ .set = 1,
+ .chmaps = (struct mp_chmap[]){ MP_CHMAP_INIT_STEREO, },
+ .num_chmaps = 1,
+ },
.samplerate = 44100,
.aformat = PCM(1, 0, 16, 0), // s16le
},
@@ -130,9 +134,14 @@ static int demux_rawaudio_open(demuxer_t *demuxer, enum demux_check check)
if (check != DEMUX_CHECK_REQUEST && check != DEMUX_CHECK_FORCE)
return -1;
+ if (opts->channels.num_chmaps != 1) {
+ MP_ERR(demuxer, "Invalid channels option given.\n");
+ return -1;
+ }
+
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_AUDIO);
struct mp_codec_params *c = sh->codec;
- c->channels = opts->channels;
+ c->channels = opts->channels.chmaps[0];
c->force_channels = true;
c->samplerate = opts->samplerate;
diff --git a/options/m_option.c b/options/m_option.c
index b7b76634f8..231b9aa5d2 100644
--- a/options/m_option.c
+++ b/options/m_option.c
@@ -2287,51 +2287,105 @@ const m_option_type_t m_option_type_afmt = {
#include "audio/chmap.h"
-static int parse_chmap(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param, void *dst)
+static int parse_channels(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, struct bstr param, void *dst)
{
- // min>0: at least min channels, min=0: empty ok
- int min_ch = (opt->flags & M_OPT_MIN) ? opt->min : 1;
- assert(min_ch >= 0);
+ // see OPT_CHANNELS for semantics.
+ bool limited = opt->min;
+
+ struct m_channels res = {0};
if (bstr_equals0(param, "help")) {
mp_chmap_print_help(log);
+ if (!limited) {
+ mp_info(log, "\nOther values:\n"
+ " auto-safe\n");
+ }
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_err(log, "Error parsing channel layout: %.*s\n", BSTR_P(param));
- return M_OPT_INVALID;
+ bool auto_safe = bstr_equals0(param, "auto-safe");
+ if (bstr_equals0(param, "auto") || bstr_equals0(param, "empty") || auto_safe) {
+ if (limited) {
+ mp_err(log, "Disallowed parameter.\n");
+ return M_OPT_INVALID;
+ }
+ param.len = 0;
+ res.set = true;
+ res.auto_safe = auto_safe;
}
- if (!mp_chmap_is_valid(&res) && !(min_ch == 0 && mp_chmap_is_empty(&res))) {
- mp_err(log, "Invalid channel layout: %.*s\n", BSTR_P(param));
- return M_OPT_INVALID;
+ while (param.len) {
+ bstr item;
+ if (limited) {
+ item = param;
+ param.len = 0;
+ } else {
+ bstr_split_tok(param, ",", &item, &param);
+ }
+
+ struct mp_chmap map = {0};
+ if (!mp_chmap_from_str(&map, item) || !mp_chmap_is_valid(&map)) {
+ mp_err(log, "Invalid channel layout: %.*s\n", BSTR_P(item));
+ talloc_free(res.chmaps);
+ return M_OPT_INVALID;
+ }
+
+ MP_TARRAY_APPEND(NULL, res.chmaps, res.num_chmaps, map);
+ res.set = true;
}
- if (dst)
- *(struct mp_chmap *)dst = res;
+ if (dst) {
+ *(struct m_channels *)dst = res;
+ } else {
+ talloc_free(res.chmaps);
+ }
return 1;
}
-static char *print_chmap(const m_option_t *opt, const void *val)
+static char *print_channels(const m_option_t *opt, const void *val)
{
- const struct mp_chmap *chmap = val;
- return talloc_strdup(NULL, mp_chmap_to_str(chmap));
+ const struct m_channels *ch = val;
+ if (!ch->set)
+ return talloc_strdup(NULL, "");
+ if (ch->auto_safe)
+ return talloc_strdup(NULL, "auto-safe");
+ if (ch->num_chmaps > 0) {
+ char *res = talloc_strdup(NULL, "");
+ for (int n = 0; n < ch->num_chmaps; n++) {
+ if (n > 0)
+ res = talloc_strdup_append(res, ",");
+ res = talloc_strdup_append(res, mp_chmap_to_str(&ch->chmaps[n]));
+ }
+ return res;
+ }
+ return talloc_strdup(NULL, "auto");
}
+static void free_channels(void *src)
+{
+ struct m_channels *ch = src;
+ talloc_free(ch->chmaps);
+ *ch = (struct m_channels){0};
+}
-const m_option_type_t m_option_type_chmap = {
+static void copy_channels(const m_option_t *opt, void *dst, const void *src)
+{
+ struct m_channels *ch = dst;
+ free_channels(dst);
+ *ch = *(struct m_channels *)src;
+ ch->chmaps =
+ talloc_memdup(NULL, ch->chmaps, sizeof(ch->chmaps[0]) * ch->num_chmaps);
+}
+
+const m_option_type_t m_option_type_channels = {
.name = "Audio channels or channel map",
- .size = sizeof(struct mp_chmap),
- .parse = parse_chmap,
- .print = print_chmap,
- .copy = copy_opt,
+ .size = sizeof(struct m_channels),
+ .parse = parse_channels,
+ .print = print_channels,
+ .copy = copy_channels,
+ .free = free_channels,
};
static int parse_timestring(struct bstr str, double *time, char endchar)
diff --git a/options/m_option.h b/options/m_option.h
index e77452af69..80be447c42 100644
--- a/options/m_option.h
+++ b/options/m_option.h
@@ -61,7 +61,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;
+extern const m_option_type_t m_option_type_channels;
extern const m_option_type_t m_option_type_node;
// Used internally by m_config.c
@@ -98,6 +98,13 @@ struct m_geometry {
void m_geometry_apply(int *xpos, int *ypos, int *widw, int *widh,
int scrw, int scrh, struct m_geometry *gm);
+struct m_channels {
+ bool set : 1;
+ bool auto_safe : 1;
+ struct mp_chmap *chmaps;
+ int num_chmaps;
+};
+
struct m_obj_desc {
// Name which will be used in the option string
const char *name;
@@ -218,7 +225,7 @@ union m_option_value {
struct m_color color;
struct m_geometry geometry;
struct m_geometry size_box;
- struct mp_chmap chmap;
+ struct m_channels channels;
};
////////////////////////////////////////////////////////////////////////////
@@ -626,9 +633,10 @@ extern const char m_option_path_separator;
#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)
-
+// If .min==1, then passing auto is disallowed, but "" is still accepted, and
+// limit channel list to 1 item.
+#define OPT_CHANNELS(...) \
+ OPT_GENERAL(struct m_channels, __VA_ARGS__, .type = &m_option_type_channels)
#define M_CHOICES(choices) \
.priv = (void *)&(const struct m_opt_choice_alternatives[]){ \
diff --git a/options/options.c b/options/options.c
index 0ad9d71b40..9fd853ffda 100644
--- a/options/options.c
+++ b/options/options.c
@@ -279,7 +279,7 @@ const m_option_t mp_opts[] = {
// force video/audio rate:
OPT_DOUBLE("fps", force_fps, CONF_MIN, .min = 0),
OPT_INTRANGE("audio-samplerate", force_srate, 0, 1000, 16*48000),
- OPT_CHMAP("audio-channels", audio_output_channels, CONF_MIN, .min = 0),
+ OPT_CHANNELS("audio-channels", audio_output_channels, 0),
OPT_AUDIOFORMAT("audio-format", audio_output_format, 0),
OPT_FLAG("audio-normalize-downmix", audio_normalize, 0),
OPT_DOUBLE("speed", playback_speed, M_OPT_RANGE | M_OPT_FIXED,
@@ -805,7 +805,6 @@ const struct MPOpts mp_default_opts = {
.sub_visibility = 1,
.sub_pos = 100,
.sub_speed = 1.0,
- .audio_output_channels = MP_CHMAP_INIT_STEREO,
.audio_output_format = 0, // AF_FORMAT_UNKNOWN
.playback_speed = 1.,
.pitch_correction = 1,
diff --git a/options/options.h b/options/options.h
index 7690823aab..ef72865f56 100644
--- a/options/options.h
+++ b/options/options.h
@@ -226,7 +226,7 @@ typedef struct MPOpts {
double force_fps;
int index_mode;
- struct mp_chmap audio_output_channels;
+ struct m_channels audio_output_channels;
int audio_output_format;
int audio_normalize;
int force_srate;
diff --git a/player/audio.c b/player/audio.c
index bcaeb13e9f..2f2bc07f0f 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -353,7 +353,10 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
} else if (af_fmt_is_pcm(in_format.format)) {
afs->output.rate = opts->force_srate;
mp_audio_set_format(&afs->output, opts->audio_output_format);
- mp_audio_set_channels(&afs->output, &opts->audio_output_channels);
+ if (opts->audio_output_channels.num_chmaps == 1) {
+ mp_audio_set_channels(&afs->output,
+ &opts->audio_output_channels.chmaps[0]);
+ }
}
// filter input format: same as codec's output format:
@@ -368,13 +371,22 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
}
if (!mpctx->ao) {
+ int ao_flags = 0;
bool spdif_fallback = af_fmt_is_spdif(afs->output.format) &&
ao_c->spdif_passthrough;
- bool ao_null_fallback = opts->ao_null_fallback && !spdif_fallback;
+
+ if (opts->ao_null_fallback && !spdif_fallback)
+ ao_flags |= AO_INIT_NULL_FALLBACK;
+
+ if (!opts->audio_output_channels.set || opts->audio_output_channels.auto_safe)
+ ao_flags |= AO_INIT_SAFE_MULTICHANNEL_ONLY;
+
+ mp_chmap_sel_list(&afs->output.channels, opts->audio_output_channels.chmaps,
+ opts->audio_output_channels.num_chmaps);
mp_audio_set_channels(&afs->output, &afs->output.channels);
- mpctx->ao = ao_init_best(mpctx->global, ao_null_fallback, mpctx->input,
+ mpctx->ao = ao_init_best(mpctx->global, ao_flags, mpctx->input,
mpctx->encode_lavc_ctx, afs->output.rate,
afs->output.format, afs->output.channels);
ao_c->ao = mpctx->ao;
diff --git a/player/main.c b/player/main.c
index 78652fe562..67dde3449f 100644
--- a/player/main.c
+++ b/player/main.c
@@ -451,11 +451,6 @@ int mp_initialize(struct MPContext *mpctx, char **options)
return -1;
}
m_config_set_profile(mpctx->mconfig, "encoding", 0);
- // never use auto
- if (!opts->audio_output_channels.num) {
- m_config_set_option_ext(mpctx->mconfig, bstr0("audio-channels"),
- bstr0("stereo"), M_SETOPT_PRESERVE_CMDLINE);
- }
mp_input_enable_section(mpctx->input, "encode", MP_INPUT_EXCLUSIVE);
}
#endif