mirror of
https://github.com/mpv-player/mpv
synced 2025-01-13 00:06:25 +01:00
audio: add channel map API
Unused, will be used in the following commits. Let chmap.h define the number of maximum channels, because that is most convenient.
This commit is contained in:
parent
1e37d35970
commit
0042735d7a
1
Makefile
1
Makefile
@ -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 \
|
||||
|
478
audio/chmap.c
Normal file
478
audio/chmap.c
Normal file
@ -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);
|
||||
}
|
133
audio/chmap.h
Normal file
133
audio/chmap.h
Normal file
@ -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
|
@ -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 {
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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[]){ \
|
||||
|
Loading…
Reference in New Issue
Block a user