From e60b8f181dec744af25c3a52fb88f600cd1b63ea Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 22 Oct 2013 01:20:43 +0200 Subject: audio/filter: split af_format into separate filters, rename af_force af_format is the old audio conversion filter. It could do all possible conversions supported by the audio chain. However, ever since the addition of af_lavrresample, most conversions are done by libav/swresample, and af_format is used as fallback. Separate out the fallback cases and remove af_format. af_convert24 does 24 bit <-> 32 bit conversions, while af_convertsignendian does sign and endian conversions. Maybe the way the conversions are split sounds a bit odd. But the former changes the size of the audio data, while the latter is fully in-place, so there's at least different buffer management. This requires a quite complicated algorithm to make sure all these "partial" conversion filters can actually get from one format to another. E.g. s24le->s32be always requires convertsignendian and convert24, but af.c has no idea what the intermediate format should be. So I added a graph search (trying every possible format and filter) to determine required format and filter. When I wrote this, it seemed this was still better than messing everything into af_lavrresample, but maybe this is overkill and I'll change my opinion. For now, it seems nice to get rid of af_format though. The AC3->IEC61937 conversion isn't supported anymore, but I don't think this is needed anywhere. Most AOs test all formats explicitly, or use the AF_FORMAT_IS_IEC61937() macro (which includes AC3). One positive consequence of this change is that conversions always include dithering (done by libav/swresample), instead of possibly going through af_format, which doesn't do anything fancy. Rename af_force to af_format. It's essentially compatible with command line uses of af_format. We retain a compatibility alias for af_force. --- audio/filter/af_convertsignendian.c | 133 ++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 audio/filter/af_convertsignendian.c (limited to 'audio/filter/af_convertsignendian.c') diff --git a/audio/filter/af_convertsignendian.c b/audio/filter/af_convertsignendian.c new file mode 100644 index 0000000000..c5e0004a50 --- /dev/null +++ b/audio/filter/af_convertsignendian.c @@ -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 . + */ + +#include +#include + +#include "af.h" +#include "audio/format.h" +#include "compat/mpbswap.h" + +static bool test_conversion(int src_format, int dst_format) +{ + int src_noend = src_format & ~AF_FORMAT_END_MASK; + int dst_noend = dst_format & ~AF_FORMAT_END_MASK; + // We can swap endian for all formats, but sign only for integer formats. + if (src_noend == dst_noend) + return true; + if (((src_noend & ~AF_FORMAT_SIGN_MASK) == + (dst_noend & ~AF_FORMAT_SIGN_MASK)) && + ((src_noend & AF_FORMAT_POINT_MASK) == AF_FORMAT_I)) + return true; + return false; +} + +static int control(struct af_instance *af, int cmd, void *arg) +{ + switch (cmd) { + case AF_CONTROL_REINIT: { + struct mp_audio *in = arg; + struct mp_audio orig_in = *in; + struct mp_audio *out = af->data; + + if (!test_conversion(in->format, out->format)) + return AF_DETACH; + + out->rate = in->rate; + mp_audio_set_channels(out, &in->channels); + + return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE; + } + case AF_CONTROL_FORMAT_FMT | AF_CONTROL_SET: { + mp_audio_set_format(af->data, *(int*)arg); + return AF_OK; + } + } + return AF_UNKNOWN; +} + +static void endian(void *data, int len, int bps) +{ + switch (bps) { + case 2: + for (int i = 0; i < len; i++) { + ((uint16_t*)data)[i] = bswap_16(((uint16_t *)data)[i]); + } + break; + case 3: + for(int i = 0; i < len; i++) { + uint8_t s = ((uint8_t *)data)[3 * i]; + ((uint8_t *)data)[3 * i] = ((uint8_t *)data)[3 * i + 2]; + ((uint8_t *)data)[3 * i + 2] = s; + } + break; + case 4: + for(int i = 0; i < len; i++) { + ((uint32_t*)data)[i] = bswap_32(((uint32_t *)data)[i]); + } + break; + } +} + +static void si2us(void *data, int len, int bps, bool le) +{ + ptrdiff_t i = -(len * bps); + uint8_t *p = &((uint8_t *)data)[len * bps]; + if (le && bps > 1) + p += bps - 1; + if (len <= 0) + return; + do { + p[i] ^= 0x80; + } while (i += bps); +} + +static struct mp_audio *play(struct af_instance *af, struct mp_audio *data) +{ + int infmt = data->format; + int outfmt = af->data->format; + size_t len = data->len / data->bps; + + if ((infmt & AF_FORMAT_END_MASK) != (outfmt & AF_FORMAT_END_MASK)) + endian(data->audio, len, data->bps); + + if ((infmt & AF_FORMAT_SIGN_MASK) != (outfmt & AF_FORMAT_SIGN_MASK)) + si2us(data->audio, len, data->bps, + (outfmt & AF_FORMAT_END_MASK) == AF_FORMAT_LE); + + mp_audio_set_format(data, outfmt); + return data; +} + +static int af_open(struct af_instance *af) +{ + af->control = control; + af->play = play; + af->mul = 1; + af->data = talloc_zero(af, struct mp_audio); + return AF_OK; +} + +struct af_info af_info_convertsignendian = { + "Convert between sample format sign/endian", + "convertsignendian", + "", + "", + 0, + af_open, + .test_conversion = test_conversion, +}; -- cgit v1.2.3