summaryrefslogtreecommitdiffstats
path: root/audio
diff options
context:
space:
mode:
Diffstat (limited to 'audio')
-rw-r--r--audio/out/ao.c69
-rw-r--r--audio/out/internal.h15
-rw-r--r--audio/out/pull.c45
3 files changed, 129 insertions, 0 deletions
diff --git a/audio/out/ao.c b/audio/out/ao.c
index b699b64c5a..12fa0a95a8 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -30,6 +30,7 @@
#include "options/options.h"
#include "options/m_config.h"
+#include "osdep/endian.h"
#include "common/msg.h"
#include "common/common.h"
#include "common/global.h"
@@ -644,3 +645,71 @@ void ao_print_devices(struct mpv_global *global, struct mp_log *log)
}
ao_hotplug_destroy(hp);
}
+
+static int get_conv_type(struct ao_convert_fmt *fmt)
+{
+ if (af_fmt_to_bytes(fmt->src_fmt) * 8 == fmt->dst_bits && !fmt->pad_msb)
+ return 0; // passthrough
+ if (fmt->src_fmt == AF_FORMAT_S32 && fmt->dst_bits == 24 && !fmt->pad_msb)
+ return 1; // simple 32->24 bit conversion
+ if (fmt->src_fmt == AF_FORMAT_S32 && fmt->dst_bits == 32 && fmt->pad_msb == 8)
+ return 2; // simple 32->24 bit conversion, with MSB padding
+ return -1; // unsupported
+}
+
+// Check whether ao_convert_inplace() can be called. As an exception, the
+// planar-ness of the sample format and the number of channels is ignored.
+// All other parameters must be as passed to ao_convert_inplace().
+bool ao_can_convert_inplace(struct ao_convert_fmt *fmt)
+{
+ return get_conv_type(fmt) >= 0;
+}
+
+bool ao_need_conversion(struct ao_convert_fmt *fmt)
+{
+ return get_conv_type(fmt) != 0;
+}
+
+// The LSB is always ignored.
+#if BYTE_ORDER == BIG_ENDIAN
+#define SHIFT24(x) ((3-(x))*8)
+#else
+#define SHIFT24(x) (((x)+1)*8)
+#endif
+
+static void convert_plane(int type, void *data, int num_samples)
+{
+ switch (type) {
+ case 0:
+ break;
+ case 1: /* fall through */
+ case 2: {
+ int bytes = type == 1 ? 3 : 4;
+ for (int s = 0; s < num_samples; s++) {
+ uint32_t val = *((uint32_t *)data + s);
+ uint8_t *ptr = (uint8_t *)data + s * bytes;
+ ptr[0] = val >> SHIFT24(0);
+ ptr[1] = val >> SHIFT24(1);
+ ptr[2] = val >> SHIFT24(2);
+ if (type == 2)
+ ptr[3] = 0;
+ }
+ break;
+ }
+ default:
+ abort();
+ }
+}
+
+// data[n] contains the pointer to the first sample of the n-th plane, in the
+// format implied by fmt->src_fmt. src_fmt also controls whether the data is
+// all in one plane, or of there is a plane per channel.
+void ao_convert_inplace(struct ao_convert_fmt *fmt, void **data, int num_samples)
+{
+ int type = get_conv_type(fmt);
+ bool planar = af_fmt_is_planar(fmt->src_fmt);
+ int planes = planar ? fmt->channels : 1;
+ int plane_samples = num_samples * (planar ? 1: fmt->channels);
+ for (int n = 0; n < planes; n++)
+ convert_plane(type, data[n], plane_samples);
+}
diff --git a/audio/out/internal.h b/audio/out/internal.h
index 0c59ce973e..28deefe128 100644
--- a/audio/out/internal.h
+++ b/audio/out/internal.h
@@ -215,4 +215,19 @@ bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
void ao_device_list_add(struct ao_device_list *list, struct ao *ao,
struct ao_device_desc *e);
+struct ao_convert_fmt {
+ int src_fmt; // source AF_FORMAT_*
+ int channels; // number of channels
+ int dst_bits; // total target data sample size
+ int pad_msb; // padding in the MSB (i.e. required shifting)
+ int pad_lsb; // padding in LSB (required 0 bits) (ignored)
+};
+
+bool ao_can_convert_inplace(struct ao_convert_fmt *fmt);
+bool ao_need_conversion(struct ao_convert_fmt *fmt);
+void ao_convert_inplace(struct ao_convert_fmt *fmt, void **data, int num_samples);
+
+int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
+ void **data, int samples, int64_t out_time_us);
+
#endif
diff --git a/audio/out/pull.c b/audio/out/pull.c
index 6da3825965..fc8dc893c3 100644
--- a/audio/out/pull.c
+++ b/audio/out/pull.c
@@ -64,6 +64,8 @@ struct ao_pull_state {
// Device delay of the last written sample, in realtime.
atomic_llong end_time_us;
+
+ char *convert_buffer;
};
static void set_state(struct ao *ao, int new_state)
@@ -180,6 +182,45 @@ end:
return bytes / ao->sstride;
}
+// Same as ao_read_data(), but read pre-converted data according to *fmt.
+// fmt->src_fmt and fmt->channels must be the same as the AO parameters.
+int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
+ void **data, int samples, int64_t out_time_us)
+{
+ assert(ao->api == &ao_api_pull);
+
+ struct ao_pull_state *p = ao->api_priv;
+ void *ndata[MP_NUM_CHANNELS];
+
+ if (!ao_need_conversion(fmt))
+ return ao_read_data(ao, data, samples, out_time_us);
+
+ assert(ao->format == fmt->src_fmt);
+ assert(ao->channels.num == fmt->channels);
+
+ bool planar = af_fmt_is_planar(fmt->src_fmt);
+ int planes = planar ? fmt->channels : 1;
+ int plane_size = af_fmt_to_bytes(fmt->src_fmt) * samples *
+ (planar ? 1: fmt->channels);
+
+ int needed = plane_size * planes * fmt->channels * samples;
+ if (needed > talloc_get_size(p->convert_buffer) || !p->convert_buffer) {
+ talloc_free(p->convert_buffer);
+ p->convert_buffer = talloc_size(NULL, needed);
+ }
+
+ for (int n = 0; n < planes; n++)
+ ndata[n] = p->convert_buffer + n * plane_size;
+
+ int res = ao_read_data(ao, ndata, samples, out_time_us);
+
+ ao_convert_inplace(fmt, ndata, samples);
+ for (int n = 0; n < planes; n++)
+ memcpy(data[n], ndata[n], plane_size);
+
+ return res;
+}
+
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
{
if (ao->driver->control)
@@ -256,7 +297,11 @@ static void drain(struct ao *ao)
static void uninit(struct ao *ao)
{
+ struct ao_pull_state *p = ao->api_priv;
+
ao->driver->uninit(ao);
+
+ talloc_free(p->convert_buffer);
}
static int init(struct ao *ao)