1
0
mirror of https://github.com/mpv-player/mpv synced 2024-12-18 04:45:33 +00:00

audio: use --audio-channels=auto behavior, except on ALSA

This commit adds an --audio-channel=auto-safe mode, and makes it the
default. This mode behaves like "auto" with most AOs, except with
ao_alsa. The intention is to allow multichannel output by default on
sane APIs. ALSA is not sane as in it's so low level that it will e.g.
configure any layout over HDMI, even if the connected A/V receiver does
not support it. The HDMI fuckup is of course not ALSA's fault, but other
audio APIs normally isolate applications from dealing with this and
require the user to globally configure the correct output layout.

This will help with other AOs too. ao_lavc (encoding) is changed to the
new semantics as well, because it used to force stereo (perhaps because
encoding mode is supposed to produce safe files for crap devices?).
Exclusive mode output on Windows might need to be adjusted accordingly,
as it grants the same kind of low level access as ALSA (requires more
research).

In addition to the things mentioned above, the --audio-channels option
is extended to accept a set of channel layouts. This is supposed to be
the correct way to configure mpv ALSA multichannel output. You need to
put a list of channel layouts that your A/V receiver supports.
This commit is contained in:
wm4 2016-08-04 20:49:20 +02:00
parent c30aa23401
commit 0b144eac39
17 changed files with 233 additions and 81 deletions

View File

@ -1150,30 +1150,51 @@ Audio
This and enabling passthrough via ``--ad`` are deprecated in favor of This and enabling passthrough via ``--ad`` are deprecated in favor of
using ``--audio-spdif=dts-hd``. using ``--audio-spdif=dts-hd``.
``--audio-channels=<auto|number|layout>`` ``--audio-channels=<auto-safe|auto|layouts>``
Request a channel layout for audio output (default: stereo). This will ask Control which audio channels are output (e.g. surround vs. stereo). There
the AO to open a device with the given channel layout. It's up to the AO are the following possibilities:
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 - ``--audio-channels=auto-safe``
channel layout. Channel numbers refer to default layouts, e.g. 2 channels Use the system's preferred channel layout. If there is none (such
refer to stereo, 6 refers to 5.1. 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 See ``--audio-channels=help`` output for defined default layouts. This also
lists speaker names, which can be used to express arbitrary channel lists speaker names, which can be used to express arbitrary channel
layouts (e.g. ``fl-fr-lfe`` is 2.1). layouts (e.g. ``fl-fr-lfe`` is 2.1).
``--audio-channels=auto`` tries to play audio using the input file's If the list of channel layouts has only 1 item, the decoder is asked to
channel layout. There is no guarantee that the audio API handles this produce according output. This sometimes triggers decoder-downmix, which
correctly. See the HDMI warning below. might be different from the normal mpv downmix. (Only some decoders support
(``empty`` is an accepted obsolete alias for ``auto``.) 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
This will also request the channel layout from the decoder. If the decoder that ``--audio-channels=stereo`` triggers decoder downmix, while ``auto``
does not support the layout, it will fall back to its native channel layout. or ``auto-safe`` never will, even if they end up selecting stereo. This
(You can use ``--ad-lavc-downmix=no`` to make the decoder always output happens because the decision whether to use decoder downmix happens long
its native layout.) Note that only some decoders support remixing audio. before the audio device is opened.
Some that do include AC-3, AAC or DTS audio.
If the channel layout of the media file (i.e. the decoder) and the AO's 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. 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 channel layout, random things can happen, such as dropping the
additional channels, or adding noise. 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>`` ``--audio-normalize-downmix=<yes|no>``
Enable/disable normalization if surround audio is downmixed to stereo Enable/disable normalization if surround audio is downmixed to stereo
(default: no). If this is disabled, downmix can cause clipping. If it's (default: no). If this is disabled, downmix can cause clipping. If it's

View File

@ -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) if (s->allow_any)
mp_msg(log, lev, " - anything\n"); 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);
}

View File

@ -47,4 +47,6 @@ bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map,
struct mp_log; struct mp_log;
void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev); 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 #endif

View File

@ -104,9 +104,9 @@ static int init(struct dec_audio *da, const char *decoder)
lavc_context->codec_type = AVMEDIA_TYPE_AUDIO; lavc_context->codec_type = AVMEDIA_TYPE_AUDIO;
lavc_context->codec_id = lavc_codec->id; 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 = 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 // Always try to set - option only exists for AC3 at the moment

View File

@ -29,10 +29,10 @@ struct priv {
int in_format; int in_format;
int in_srate; int in_srate;
struct mp_chmap in_channels; struct m_channels in_channels;
int out_format; int out_format;
int out_srate; int out_srate;
struct mp_chmap out_channels; struct m_channels out_channels;
int fail; 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) if (priv->in_format != AF_FORMAT_UNKNOWN)
mp_audio_set_format(in, priv->in_format); mp_audio_set_format(in, priv->in_format);
if (priv->in_channels.num) if (priv->in_channels.num_chmaps > 0)
mp_audio_set_channels(in, &priv->in_channels); mp_audio_set_channels(in, &priv->in_channels.chmaps[0]);
if (priv->in_srate) if (priv->in_srate)
in->rate = 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) if (priv->out_format != AF_FORMAT_UNKNOWN)
mp_audio_set_format(out, priv->out_format); mp_audio_set_format(out, priv->out_format);
if (priv->out_channels.num) if (priv->out_channels.num_chmaps > 0)
mp_audio_set_channels(out, &priv->out_channels); mp_audio_set_channels(out, &priv->out_channels.chmaps[0]);
if (priv->out_srate) if (priv->out_srate)
out->rate = priv->out_srate; out->rate = priv->out_srate;
@ -124,10 +124,10 @@ const struct af_info af_info_format = {
.options = (const struct m_option[]) { .options = (const struct m_option[]) {
OPT_AUDIOFORMAT("format", in_format, 0), OPT_AUDIOFORMAT("format", in_format, 0),
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000), 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_AUDIOFORMAT("out-format", out_format, 0),
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000), 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), OPT_FLAG("fail", fail, 0),
{0} {0}
}, },

View File

@ -158,7 +158,7 @@ error:
static struct ao *ao_init(bool probing, struct mpv_global *global, static struct ao *ao_init(bool probing, struct mpv_global *global,
struct input_ctx *input_ctx, 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, int samplerate, int format, struct mp_chmap channels,
char *dev, char *name, char **args) 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->channels = channels;
ao->format = format; ao->format = format;
ao->encode_lavc_ctx = encode_lavc_ctx; ao->encode_lavc_ctx = encode_lavc_ctx;
ao->init_flags = flags;
if (ao->driver->encode != !!ao->encode_lavc_ctx) if (ao->driver->encode != !!ao->encode_lavc_ctx)
goto fail; 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(redirect, sizeof(redirect), "%s", ao->redirect);
snprintf(rdevice, sizeof(rdevice), "%s", ao->device ? ao->device : ""); snprintf(rdevice, sizeof(rdevice), "%s", ao->device ? ao->device : "");
talloc_free(ao); 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); samplerate, format, channels, rdevice, redirect, NULL);
} }
goto fail; 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, struct ao *ao_init_best(struct mpv_global *global,
bool ao_null_fallback, int init_flags,
struct input_ctx *input_ctx, struct input_ctx *input_ctx,
struct encode_lavc_context *encode_lavc_ctx, struct encode_lavc_context *encode_lavc_ctx,
int samplerate, int format, struct mp_chmap channels) 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, MP_TARRAY_APPEND(tmp, ao_list, ao_num,
(struct m_obj_settings){.name = "null"}); (struct m_obj_settings){.name = "null"});
} }
@ -297,7 +298,7 @@ struct ao *ao_init_best(struct mpv_global *global,
dev = pref_dev; dev = pref_dev;
mp_verbose(log, "Using preferred device '%s'\n", 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, samplerate, format, channels, dev,
entry->name, entry->attribs); entry->name, entry->attribs);
if (ao) if (ao)
@ -429,6 +430,29 @@ bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
return r; 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, bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
struct mp_chmap *map, int num) struct mp_chmap *map, int num)
{ {

View File

@ -50,6 +50,14 @@ enum {
AO_EVENT_HOTPLUG = 2, 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 { typedef struct ao_control_vol {
float left; float left;
float right; float right;
@ -72,7 +80,7 @@ struct encode_lavc_context;
struct mp_audio; struct mp_audio;
struct ao *ao_init_best(struct mpv_global *global, struct ao *ao_init_best(struct mpv_global *global,
bool ao_null_fallback, int init_flags,
struct input_ctx *input_ctx, struct input_ctx *input_ctx,
struct encode_lavc_context *encode_lavc_ctx, struct encode_lavc_context *encode_lavc_ctx,
int samplerate, int format, struct mp_chmap channels); int samplerate, int format, struct mp_chmap channels);

View File

@ -360,7 +360,7 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
snd_pcm_free_chmaps(maps); 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 // Map back our selected channel layout to an ALSA one. This is done this way so

View File

@ -123,7 +123,7 @@ static int init(struct ao *ao)
struct mp_chmap_sel sel = {0}; struct mp_chmap_sel sel = {0};
mp_chmap_sel_add_any(&sel); 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; goto fail;
mp_chmap_reorder_to_lavc(&ao->channels); mp_chmap_reorder_to_lavc(&ao->channels);
ac->codec->channels = ao->channels.num; ac->codec->channels = ao->channels.num;

View File

@ -43,6 +43,7 @@ struct ao {
struct encode_lavc_context *encode_lavc_ctx; struct encode_lavc_context *encode_lavc_ctx;
struct input_ctx *input_ctx; struct input_ctx *input_ctx;
struct mp_log *log; // Using e.g. "[ao/coreaudio]" as prefix 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 // 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 // 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, bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
struct mp_chmap *map); 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, bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
struct mp_chmap *map, int num); struct mp_chmap *map, int num);

View File

@ -36,7 +36,7 @@
#include "osdep/endian.h" #include "osdep/endian.h"
struct demux_rawaudio_opts { struct demux_rawaudio_opts {
struct mp_chmap channels; struct m_channels channels;
int samplerate; int samplerate;
int aformat; int aformat;
}; };
@ -49,7 +49,7 @@ struct demux_rawaudio_opts {
#define OPT_BASE_STRUCT struct demux_rawaudio_opts #define OPT_BASE_STRUCT struct demux_rawaudio_opts
const struct m_sub_options demux_rawaudio_conf = { const struct m_sub_options demux_rawaudio_conf = {
.opts = (const m_option_t[]) { .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_INTRANGE("rate", samplerate, 0, 1000, 8 * 48000),
OPT_CHOICE("format", aformat, 0, OPT_CHOICE("format", aformat, 0,
({"u8", PCM(0, 0, 8, 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), .size = sizeof(struct demux_rawaudio_opts),
.defaults = &(const struct demux_rawaudio_opts){ .defaults = &(const struct demux_rawaudio_opts){
// Note that currently, stream_cdda expects exactly these parameters! // 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, .samplerate = 44100,
.aformat = PCM(1, 0, 16, 0), // s16le .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) if (check != DEMUX_CHECK_REQUEST && check != DEMUX_CHECK_FORCE)
return -1; 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 sh_stream *sh = demux_alloc_sh_stream(STREAM_AUDIO);
struct mp_codec_params *c = sh->codec; struct mp_codec_params *c = sh->codec;
c->channels = opts->channels; c->channels = opts->channels.chmaps[0];
c->force_channels = true; c->force_channels = true;
c->samplerate = opts->samplerate; c->samplerate = opts->samplerate;

View File

@ -2287,51 +2287,105 @@ const m_option_type_t m_option_type_afmt = {
#include "audio/chmap.h" #include "audio/chmap.h"
static int parse_chmap(struct mp_log *log, const m_option_t *opt, static int parse_channels(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param, void *dst) struct bstr name, struct bstr param, void *dst)
{ {
// min>0: at least min channels, min=0: empty ok // see OPT_CHANNELS for semantics.
int min_ch = (opt->flags & M_OPT_MIN) ? opt->min : 1; bool limited = opt->min;
assert(min_ch >= 0);
struct m_channels res = {0};
if (bstr_equals0(param, "help")) { if (bstr_equals0(param, "help")) {
mp_chmap_print_help(log); mp_chmap_print_help(log);
if (!limited) {
mp_info(log, "\nOther values:\n"
" auto-safe\n");
}
return M_OPT_EXIT - 1; return M_OPT_EXIT - 1;
} }
if (param.len == 0 && min_ch >= 1) bool auto_safe = bstr_equals0(param, "auto-safe");
return M_OPT_MISSING_PARAM; if (bstr_equals0(param, "auto") || bstr_equals0(param, "empty") || auto_safe) {
if (limited) {
struct mp_chmap res = {0}; mp_err(log, "Disallowed parameter.\n");
if (!mp_chmap_from_str(&res, param)) { return M_OPT_INVALID;
mp_err(log, "Error parsing channel layout: %.*s\n", BSTR_P(param)); }
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))) { while (param.len) {
mp_err(log, "Invalid channel layout: %.*s\n", BSTR_P(param)); bstr item;
return M_OPT_INVALID; 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) if (dst) {
*(struct mp_chmap *)dst = res; *(struct m_channels *)dst = res;
} else {
talloc_free(res.chmaps);
}
return 1; 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; const struct m_channels *ch = val;
return talloc_strdup(NULL, mp_chmap_to_str(chmap)); 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", .name = "Audio channels or channel map",
.size = sizeof(struct mp_chmap), .size = sizeof(struct m_channels),
.parse = parse_chmap, .parse = parse_channels,
.print = print_chmap, .print = print_channels,
.copy = copy_opt, .copy = copy_channels,
.free = free_channels,
}; };
static int parse_timestring(struct bstr str, double *time, char endchar) static int parse_timestring(struct bstr str, double *time, char endchar)

View File

@ -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_color;
extern const m_option_type_t m_option_type_geometry; 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_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; extern const m_option_type_t m_option_type_node;
// Used internally by m_config.c // 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, void m_geometry_apply(int *xpos, int *ypos, int *widw, int *widh,
int scrw, int scrh, struct m_geometry *gm); 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 { struct m_obj_desc {
// Name which will be used in the option string // Name which will be used in the option string
const char *name; const char *name;
@ -218,7 +225,7 @@ union m_option_value {
struct m_color color; struct m_color color;
struct m_geometry geometry; struct m_geometry geometry;
struct m_geometry size_box; 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(...) \ #define OPT_AUDIOFORMAT(...) \
OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_afmt) OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_afmt)
#define OPT_CHMAP(...) \ // If .min==1, then passing auto is disallowed, but "" is still accepted, and
OPT_GENERAL(struct mp_chmap, __VA_ARGS__, .type = &m_option_type_chmap) // 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) \ #define M_CHOICES(choices) \
.priv = (void *)&(const struct m_opt_choice_alternatives[]){ \ .priv = (void *)&(const struct m_opt_choice_alternatives[]){ \

View File

@ -279,7 +279,7 @@ const m_option_t mp_opts[] = {
// force video/audio rate: // force video/audio rate:
OPT_DOUBLE("fps", force_fps, CONF_MIN, .min = 0), OPT_DOUBLE("fps", force_fps, CONF_MIN, .min = 0),
OPT_INTRANGE("audio-samplerate", force_srate, 0, 1000, 16*48000), 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_AUDIOFORMAT("audio-format", audio_output_format, 0),
OPT_FLAG("audio-normalize-downmix", audio_normalize, 0), OPT_FLAG("audio-normalize-downmix", audio_normalize, 0),
OPT_DOUBLE("speed", playback_speed, M_OPT_RANGE | M_OPT_FIXED, 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_visibility = 1,
.sub_pos = 100, .sub_pos = 100,
.sub_speed = 1.0, .sub_speed = 1.0,
.audio_output_channels = MP_CHMAP_INIT_STEREO,
.audio_output_format = 0, // AF_FORMAT_UNKNOWN .audio_output_format = 0, // AF_FORMAT_UNKNOWN
.playback_speed = 1., .playback_speed = 1.,
.pitch_correction = 1, .pitch_correction = 1,

View File

@ -226,7 +226,7 @@ typedef struct MPOpts {
double force_fps; double force_fps;
int index_mode; int index_mode;
struct mp_chmap audio_output_channels; struct m_channels audio_output_channels;
int audio_output_format; int audio_output_format;
int audio_normalize; int audio_normalize;
int force_srate; int force_srate;

View File

@ -353,7 +353,10 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
} else if (af_fmt_is_pcm(in_format.format)) { } else if (af_fmt_is_pcm(in_format.format)) {
afs->output.rate = opts->force_srate; afs->output.rate = opts->force_srate;
mp_audio_set_format(&afs->output, opts->audio_output_format); 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: // 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) { if (!mpctx->ao) {
int ao_flags = 0;
bool spdif_fallback = af_fmt_is_spdif(afs->output.format) && bool spdif_fallback = af_fmt_is_spdif(afs->output.format) &&
ao_c->spdif_passthrough; 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); 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, mpctx->encode_lavc_ctx, afs->output.rate,
afs->output.format, afs->output.channels); afs->output.format, afs->output.channels);
ao_c->ao = mpctx->ao; ao_c->ao = mpctx->ao;

View File

@ -451,11 +451,6 @@ int mp_initialize(struct MPContext *mpctx, char **options)
return -1; return -1;
} }
m_config_set_profile(mpctx->mconfig, "encoding", 0); 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); mp_input_enable_section(mpctx->input, "encode", MP_INPUT_EXCLUSIVE);
} }
#endif #endif