summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2015-05-04 23:59:20 +0200
committerDiogo Franco (Kovensky) <diogomfranco@gmail.com>2015-05-07 10:47:00 +0900
commit604daa861fa6cda1b118a20e06269b1a4a6c3f21 (patch)
treed7923ea0b6b27fb7250dec9228f95b31b5354cd0
parent322c79a6e1bac2e8f1a5e042ac0b635c9dae629f (diff)
downloadmpv-604daa861fa6cda1b118a20e06269b1a4a6c3f21.tar.bz2
mpv-604daa861fa6cda1b118a20e06269b1a4a6c3f21.tar.xz
audio: introduce support for padding channels
Some audio APIs explicitly require you to add dummy channels. These are not rendered, and only exist for the sake of the audio API or hardware strangeness. At least ALSA, Sndio, and CoreAudio seem to have them. This commit is preparation for using them with ao_coreaudio. The result is a bit messy. libavresample/libswresample don't have good API for this; avresample_set_channel_mapping() is pretty useless. Although in theory you can use it to add and remove channels, you can't set the channel counts. So we do the ordering ourselves by making sure the audio data is planar, and by swapping the plane pointers. This requires lots of messiness to get the conversions in place. Also, the input reordering is still done with the "old" method, and doesn't support padded channels - hopefully this will never be needed. (I tried to come up with cleaner solutions, but compared to my other attempts, the final commit is not that bad.) (cherry picked from commit 06050aed9906b784159ad03e86e13348c4d9fa47)
-rw-r--r--audio/chmap.c70
-rw-r--r--audio/chmap.h9
-rw-r--r--audio/filter/af_lavrresample.c119
3 files changed, 142 insertions, 56 deletions
diff --git a/audio/chmap.c b/audio/chmap.c
index aec6a9aa05..7e62dab74a 100644
--- a/audio/chmap.c
+++ b/audio/chmap.c
@@ -190,6 +190,33 @@ void mp_chmap_reorder_norm(struct mp_chmap *map)
qsort(arr, map->num, 1, comp_uint8);
}
+// Remove silent (NA) channels, if any.
+void mp_chmap_remove_na(struct mp_chmap *map)
+{
+ struct mp_chmap new = {0};
+ for (int n = 0; n < map->num; n++) {
+ int sp = map->speaker[n];
+ if (!(sp >= MP_SPEAKER_ID_NA0 && sp <= MP_SPEAKER_ID_NA_LAST))
+ new.speaker[new.num++] = map->speaker[n];
+ }
+ *map = new;
+}
+
+// Add silent (NA) channels to map until map->num >= num.
+void mp_chmap_fill_na(struct mp_chmap *map, int num)
+{
+ assert(num <= MP_NUM_CHANNELS);
+ while (map->num < num) {
+ int sp = map->num ? map->speaker[map->num - 1] : -1;
+ if (sp >= MP_SPEAKER_ID_NA0 && sp < MP_SPEAKER_ID_NA_LAST) {
+ sp += 1;
+ } else {
+ sp = MP_SPEAKER_ID_NA0;
+ }
+ map->speaker[map->num++] = sp;
+ }
+}
+
// 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.
@@ -279,7 +306,7 @@ uint64_t mp_chmap_to_lavc_unchecked(const struct mp_chmap *src)
mp_chmap_from_channels(&t, t.num);
uint64_t mask = 0;
for (int n = 0; n < t.num; n++) {
- if (t.speaker[n] < 64)
+ if (t.speaker[n] < 64) // ignore MP_SPEAKER_ID_NA* etc.
mask |= 1ULL << t.speaker[n];
}
return mask;
@@ -342,33 +369,35 @@ void mp_chmap_reorder_to_lavc(struct mp_chmap *map)
// 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,
+// for each speaker n, src[n] will be set such that:
+// to->speaker[n] = from->speaker[src[n]]
+// (src[n] gives the source channel for destination channel n)
+// If *from and *to don't contain the same set of speakers, then the above
+// invariant is not guaranteed. Instead, src[n] can be set to -1 if the channel
+// at to->speaker[n] is unmapped.
+void mp_chmap_get_reorder(int src[MP_NUM_CHANNELS], const struct mp_chmap *from,
const struct mp_chmap *to)
{
- assert(from->num == to->num);
+ for (int n = 0; n < MP_NUM_CHANNELS; n++)
+ src[n] = -1;
+
if (mp_chmap_is_unknown(from) || mp_chmap_is_unknown(to)) {
- for (int n = 0; n < from->num; n++)
- dst[n] = n;
+ for (int n = 0; n < to->num; n++)
+ src[n] = n < from->num ? n : -1;
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;
+ if (to->speaker[n] == from->speaker[i]) {
+ src[n] = i;
break;
}
}
- assert(dst[n] != -1);
}
- for (int n = 0; n < from->num; n++)
- assert(to->speaker[dst[n]] == from->speaker[n]);
+
+ for (int n = 0; n < to->num; n++)
+ assert(to->speaker[n] == src[n] < 0 ? -1 : from->speaker[src[n]]);
}
// Performs the difference between a and b, and store it in diff. If b has
@@ -411,7 +440,12 @@ char *mp_chmap_to_str_buf(char *buf, size_t buf_size, const struct mp_chmap *src
const char *s = sp < MP_SPEAKER_ID_COUNT ? speaker_names[sp][0] : NULL;
char sp_buf[10];
if (!s) {
- snprintf(sp_buf, sizeof(sp_buf), "sp%d", sp);
+ const char *prefix = "sp";
+ if (sp >= MP_SPEAKER_ID_NA0 && sp <= MP_SPEAKER_ID_NA_LAST) {
+ sp -= MP_SPEAKER_ID_NA0;
+ prefix = "na";
+ }
+ snprintf(sp_buf, sizeof(sp_buf), "%s%d", prefix, sp);
s = sp_buf;
}
mp_snprintf_cat(buf, buf_size, "%s%s", n > 0 ? "-" : "", s);
diff --git a/audio/chmap.h b/audio/chmap.h
index 1ae4fb0697..296b2a1712 100644
--- a/audio/chmap.h
+++ b/audio/chmap.h
@@ -61,6 +61,11 @@ enum mp_speaker_id {
MP_SPEAKER_ID_UNKNOWN0 = 64,
MP_SPEAKER_ID_UNKNOWN_LAST = MP_SPEAKER_ID_UNKNOWN0 + MP_NUM_CHANNELS - 1,
+ // "Silent" channels. These are sometimes used to insert padding for
+ // unused channels.
+ MP_SPEAKER_ID_NA0,
+ MP_SPEAKER_ID_NA_LAST = MP_SPEAKER_ID_NA0 + MP_NUM_CHANNELS - 1,
+
// Including the unassigned IDs in between. This is not a valid ID anymore,
// but is still within uint8_t.
MP_SPEAKER_ID_COUNT,
@@ -101,6 +106,8 @@ bool mp_chmap_equals_reordered(const struct mp_chmap *a, const struct mp_chmap *
bool mp_chmap_is_stereo(const struct mp_chmap *src);
void mp_chmap_reorder_norm(struct mp_chmap *map);
+void mp_chmap_remove_na(struct mp_chmap *map);
+void mp_chmap_fill_na(struct mp_chmap *map, int num);
void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels);
void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels);
@@ -116,7 +123,7 @@ 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_get_reorder(int dst[MP_NUM_CHANNELS], const struct mp_chmap *from,
+void mp_chmap_get_reorder(int src[MP_NUM_CHANNELS], const struct mp_chmap *from,
const struct mp_chmap *to);
void mp_chmap_diff(const struct mp_chmap *a, const struct mp_chmap *b,
diff --git a/audio/filter/af_lavrresample.c b/audio/filter/af_lavrresample.c
index 412846a78e..ba67eb1c0c 100644
--- a/audio/filter/af_lavrresample.c
+++ b/audio/filter/af_lavrresample.c
@@ -81,6 +81,7 @@ struct af_resample {
struct mp_audio *pending;
bool avrctx_ok;
struct AVAudioResampleContext *avrctx;
+ struct mp_audio avrctx_fmt; // output format of avrctx
struct AVAudioResampleContext *avrctx_out; // for output channel reordering
struct af_resample_opts ctx; // opts in the context
struct af_resample_opts opts; // opts requested by the user
@@ -163,6 +164,22 @@ static bool test_conversion(int src_format, int dst_format)
af_to_avformat(dst_format) != AV_SAMPLE_FMT_NONE;
}
+// mp_chmap_get_reorder() performs:
+// to->speaker[n] = from->speaker[src[n]]
+// but libavresample does:
+// to->speaker[dst[n]] = from->speaker[n]
+static void transpose_order(int *map, int num)
+{
+ int nmap[MP_NUM_CHANNELS] = {0};
+ for (int n = 0; n < num; n++) {
+ for (int i = 0; i < num; i++) {
+ if (map[n] == i)
+ nmap[i] = n;
+ }
+ }
+ memcpy(map, nmap, sizeof(nmap));
+}
+
static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
struct mp_audio *out)
{
@@ -172,8 +189,12 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
enum AVSampleFormat in_samplefmt = af_to_avformat(in->format);
enum AVSampleFormat out_samplefmt = af_to_avformat(out->format);
+ enum AVSampleFormat out_samplefmtp =
+ af_to_avformat(af_fmt_to_planar(out->format));
- if (in_samplefmt == AV_SAMPLE_FMT_NONE || out_samplefmt == AV_SAMPLE_FMT_NONE)
+ if (in_samplefmt == AV_SAMPLE_FMT_NONE ||
+ out_samplefmt == AV_SAMPLE_FMT_NONE ||
+ out_samplefmtp == AV_SAMPLE_FMT_NONE)
return AF_ERROR;
avresample_close(s->avrctx);
@@ -220,27 +241,52 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
uint64_t in_ch_layout = mp_chmap_to_lavc_unchecked(&map_in);
uint64_t out_ch_layout = mp_chmap_to_lavc_unchecked(&map_out);
- av_opt_set_int(s->avrctx, "in_channel_layout", in_ch_layout, 0);
- av_opt_set_int(s->avrctx, "out_channel_layout", out_ch_layout, 0);
-
- av_opt_set_int(s->avrctx, "in_sample_rate", s->ctx.in_rate, 0);
- av_opt_set_int(s->avrctx, "out_sample_rate", s->ctx.out_rate, 0);
-
- av_opt_set_int(s->avrctx, "in_sample_fmt", in_samplefmt, 0);
- av_opt_set_int(s->avrctx, "out_sample_fmt", out_samplefmt, 0);
-
struct mp_chmap in_lavc;
mp_chmap_from_lavc(&in_lavc, in_ch_layout);
+ if (in_lavc.num != map_in.num) {
+ // For handling NA channels, we would have to add a planarization step.
+ MP_FATAL(af, "Unsupported channel remapping.\n");
+ return AF_ERROR;
+ }
+
mp_chmap_get_reorder(s->reorder_in, &map_in, &in_lavc);
+ transpose_order(s->reorder_in, map_in.num);
struct mp_chmap out_lavc;
mp_chmap_from_lavc(&out_lavc, out_ch_layout);
- mp_chmap_get_reorder(s->reorder_out, &out_lavc, &map_out);
+ if (mp_chmap_equals(&out_lavc, &map_out)) {
+ // No intermediate step required - output new format directly.
+ out_samplefmtp = out_samplefmt;
+ } else {
+ // Verify that we really just reorder and/or insert NA channels.
+ struct mp_chmap withna = out_lavc;
+ mp_chmap_fill_na(&withna, map_out.num);
+ if (withna.num != map_out.num)
+ return AF_ERROR;
+ mp_chmap_get_reorder(s->reorder_out, &out_lavc, &map_out);
+ }
- // Same configuration; we just reorder.
- av_opt_set_int(s->avrctx_out, "in_channel_layout", out_ch_layout, 0);
- av_opt_set_int(s->avrctx_out, "out_channel_layout", out_ch_layout, 0);
- av_opt_set_int(s->avrctx_out, "in_sample_fmt", out_samplefmt, 0);
+ s->avrctx_fmt = *out;
+ mp_audio_set_channels(&s->avrctx_fmt, &out_lavc);
+ mp_audio_set_format(&s->avrctx_fmt, af_from_avformat(out_samplefmtp));
+
+ // Real conversion; output is input to avrctx_out.
+ av_opt_set_int(s->avrctx, "in_channel_layout", in_ch_layout, 0);
+ av_opt_set_int(s->avrctx, "out_channel_layout", out_ch_layout, 0);
+ av_opt_set_int(s->avrctx, "in_sample_rate", s->ctx.in_rate, 0);
+ av_opt_set_int(s->avrctx, "out_sample_rate", s->ctx.out_rate, 0);
+ av_opt_set_int(s->avrctx, "in_sample_fmt", in_samplefmt, 0);
+ av_opt_set_int(s->avrctx, "out_sample_fmt", out_samplefmtp, 0);
+
+ // Just needs the correct number of channels.
+ int fake_out_ch_layout = av_get_default_channel_layout(map_out.num);
+ if (!fake_out_ch_layout)
+ return AF_ERROR;
+
+ // Deplanarize if needed.
+ av_opt_set_int(s->avrctx_out, "in_channel_layout", fake_out_ch_layout, 0);
+ av_opt_set_int(s->avrctx_out, "out_channel_layout", fake_out_ch_layout, 0);
+ av_opt_set_int(s->avrctx_out, "in_sample_fmt", out_samplefmtp, 0);
av_opt_set_int(s->avrctx_out, "out_sample_fmt", out_samplefmt, 0);
av_opt_set_int(s->avrctx_out, "in_sample_rate", s->ctx.out_rate, 0);
av_opt_set_int(s->avrctx_out, "out_sample_rate", s->ctx.out_rate, 0);
@@ -249,7 +295,6 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
// * This function can only be called when the allocated context is not open.
// * Also, the input channel layout must have already been set.
avresample_set_channel_mapping(s->avrctx, s->reorder_in);
- avresample_set_channel_mapping(s->avrctx_out, s->reorder_out);
if (avresample_open(s->avrctx) < 0 ||
avresample_open(s->avrctx_out) < 0)
@@ -354,22 +399,22 @@ static void uninit(struct af_instance *af)
talloc_free(s->pending);
}
-static bool needs_reorder(int *reorder, int num_ch)
-{
- for (int n = 0; n < num_ch; n++) {
- if (reorder[n] != n)
- return true;
- }
- return false;
-}
-
-static void reorder_planes(struct mp_audio *mpa, int *reorder)
+static void reorder_planes(struct mp_audio *mpa, int *reorder,
+ struct mp_chmap *newmap)
{
struct mp_audio prev = *mpa;
- for (int n = 0; n < mpa->num_planes; n++) {
- assert(reorder[n] >= 0 && reorder[n] < mpa->num_planes);
- mpa->planes[n] = prev.planes[reorder[n]];
+
+ for (int n = 0; n < newmap->num; n++) {
+ int src = reorder[n];
+ if (src < 0) {
+ src = 0; // Use the first plane for padding channels.
+ mpa->readonly = true;
+ }
+ assert(src < mpa->num_planes);
+ mpa->planes[n] = prev.planes[src];
}
+
+ mp_audio_set_channels(mpa, newmap);
}
static int filter(struct af_instance *af, struct mp_audio *in)
@@ -388,14 +433,14 @@ static int filter(struct af_instance *af, struct mp_audio *in)
int samples = avresample_available(s->avrctx) +
av_rescale_rnd(get_delay(s) + (in ? in->samples : 0),
s->ctx.out_rate, s->ctx.in_rate, AV_ROUND_UP);
- struct mp_audio *out = mp_audio_pool_get(af->out_pool, af->data, samples);
+
+ struct mp_audio out_format = s->avrctx_fmt;
+ struct mp_audio *out = mp_audio_pool_get(af->out_pool, &out_format, samples);
if (!out)
goto error;
if (in)
mp_audio_copy_attributes(out, in);
- mp_audio_realloc_min(out, out->samples);
-
af->delay = get_delay(s) / (double)s->ctx.in_rate;
if (out->samples) {
@@ -404,11 +449,11 @@ static int filter(struct af_instance *af, struct mp_audio *in)
goto error;
}
- if (needs_reorder(s->reorder_out, out->nch)) {
- if (AF_FORMAT_IS_PLANAR(out->format)) {
- reorder_planes(out, s->reorder_out);
- } else if (out->samples) {
- struct mp_audio *new = mp_audio_pool_get(s->reorder_buffer, out,
+ if (out->samples && !mp_audio_config_equals(out, af->data)) {
+ assert(AF_FORMAT_IS_PLANAR(out->format));
+ reorder_planes(out, s->reorder_out, &af->data->channels);
+ if (!mp_audio_config_equals(out, af->data)) {
+ struct mp_audio *new = mp_audio_pool_get(s->reorder_buffer, af->data,
out->samples);
if (!new)
goto error;