From: Peter Meerwald <p.meerwald@xxxxxxxxxxxxxxxxxx> v2: * add ARM NEON stereo-to-mono remapping code * static __attribute__ ((noinline)) is necessary to prevent inlining and work around gcc 4.6 ICE, see https://bugs.launchpad.net/bugs/936863 * call test code, the reference implementation is obtained using pa_get_init_remap_func() * remove check for NEON flags v1: * ARM NEON mono-to-stereo remapping code compiled with Ubuntu/Linaro gcc 4.6.1: arm-linux-gnueabi-gcc-O2 -mcpu=cortex-a8 -mfloat-abi=softfp -mfpu=neon runtime on beagle-xm, 800 MHz (ref is the special-case C impl, generic is the catch-all matrix implementation): checking NEON remap_mono_to_stereo(float) NEON: 29999 usec. ref: 41078 usec. generic: 713206 usec. checking NEON remap_stereo_to_mono(float) NEON: 29114 usec. ref: 237216 usec. generic: 823254 usec. checking NEON remap_mono_to_stereo(s16) NEON: 15136 usec. ref: 37690 usec. generic: 128878 usec. checking NEON remap_stereo_to_mono(s16) NEON: 12024 usec. ref: 49104 usec. generic: 122439 usec. --- src/Makefile.am | 1 + src/pulsecore/remap_neon.c | 428 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 429 insertions(+), 0 deletions(-) create mode 100644 src/pulsecore/remap_neon.c diff --git a/src/Makefile.am b/src/Makefile.am index 603ccc3..a6d1644 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -813,6 +813,7 @@ libpulsecore_ at PA_MAJORMINOR@_la_SOURCES = \ pulsecore/play-memchunk.c pulsecore/play-memchunk.h \ pulsecore/remap.c pulsecore/remap.h \ pulsecore/remap_mmx.c pulsecore/remap_sse.c \ + pulsecore/remap_neon.c \ pulsecore/resampler.c pulsecore/resampler.h \ pulsecore/rtpoll.c pulsecore/rtpoll.h \ pulsecore/sample-util.c pulsecore/sample-util.h \ diff --git a/src/pulsecore/remap_neon.c b/src/pulsecore/remap_neon.c new file mode 100644 index 0000000..675fb6a --- /dev/null +++ b/src/pulsecore/remap_neon.c @@ -0,0 +1,428 @@ +/*** + This file is part of PulseAudio. + + Copyright 2012 Peter Meerwald <p.meerwald at bct-electronic.com> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio 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 Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/rtclock.h> +#include <pulse/sample.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "cpu-arm.h" +#include "remap.h" + +#if defined(__ARM_NEON__) + +#include <math.h> +#include <arm_neon.h> + +#define RUN_TEST + +static __attribute__ ((noinline)) void mono_to_stereo_float(float *d, const float *s, unsigned n) { + unsigned i; + + for (i = 0; i < n/4; i++) { + float32x4x2_t stereo; + stereo.val[0] = vld1q_f32(s); + stereo.val[1] = stereo.val[0]; + vst2q_f32(d, stereo); + s += 4; + d += 8; + } + + for (i = n & ~3; i < n; i++) { + d[0] = d[1] = s[0]; + s++; + d += 2; + } +} + +static __attribute__ ((noinline)) void mono_to_stereo_int16(int16_t *d, const int16_t *s, unsigned n) { + unsigned i; + + for (i = 0; i < n/8; i++) { + int16x8x2_t stereo; + stereo.val[0] = vld1q_s16(s); + stereo.val[1] = stereo.val[0]; + vst2q_s16(d, stereo); + s += 8; + d += 16; + } + + for (i = n & ~7; i < n; i++) { + d[0] = d[1] = s[0]; + s++; + d += 2; + } +} + +static void remap_mono_to_stereo_neon(pa_remap_t *m, void *dst, const void *src, unsigned n) { + switch (*m->format) { + case PA_SAMPLE_FLOAT32NE: + mono_to_stereo_float(dst, src, n); + break; + case PA_SAMPLE_S16NE: + mono_to_stereo_int16(dst, src, n); + break; + default: + pa_assert_not_reached(); + } +} + +/* don't inline, causes ICE, see https://bugs.launchpad.net/bugs/936863 */ +static __attribute__ ((noinline)) void stereo_to_mono_float(float *d, const float *s, unsigned n) { + unsigned i; + + for (i = 0; i < n/4; i++) { + float32x4x2_t stereo = vld2q_f32(s); + float32x4_t mono = vaddq_f32(stereo.val[0], stereo.val[1]); + vst1q_f32(d, mono); + s += 8; + d += 4; + } + for (i = n & ~3; i < n; i++) { + d[0] = s[0] + s[1]; + s += 2; + d++; + } +} + +/* don't inline, causes ICE, see https://bugs.launchpad.net/bugs/936863 */ +static __attribute__ ((noinline)) void stereo_to_mono_int16(int16_t *d, const int16_t *s, unsigned n) { + unsigned int i; + + for (i = 0; i < n/8; i++) { + int16x8x2_t stereo = vld2q_s16(s); + int16x8_t mono = vaddq_s16(stereo.val[0], stereo.val[1]); + vst1q_s16(d, mono); + s += 16; + d += 8; + } + for (i = n & ~7; i < n; i++) { + d[0] = s[0] + s[1]; + s += 2; + d++; + } +} + +static void remap_stereo_to_mono_neon(pa_remap_t *m, void *dst, const void *src, unsigned n) { + switch (*m->format) { + case PA_SAMPLE_FLOAT32NE: + stereo_to_mono_float(dst, src, n); + break; + case PA_SAMPLE_S16NE: + stereo_to_mono_int16(dst, src, n); + break; + default: + pa_assert_not_reached(); + } +} + +#ifdef RUN_TEST +#define SAMPLES 1019 +#define TIMES 1000 + +static void run_test_float_mono_to_stereo(void) { + float stereo[2*SAMPLES]; + float stereo_ref[2*SAMPLES]; + float mono[SAMPLES]; + int i; + pa_usec_t start, stop; + pa_sample_format_t sf; + pa_remap_t remap; + pa_sample_spec iss, oss; + + pa_init_remap_func_t remap_init_func = pa_get_init_remap_func(); + + pa_log_debug("checking NEON remap_mono_to_stereo(float)"); + + memset(stereo_ref, 0, sizeof(stereo_ref)); + memset(stereo, 0, sizeof(stereo)); + + for (i = 0; i < SAMPLES; i++) { + mono[i] = rand()/(float) RAND_MAX - 0.5f; + } + + sf = PA_SAMPLE_FLOAT32NE; + remap.format = &sf; + iss.format = PA_SAMPLE_FLOAT32NE; + iss.channels = 1; + oss.format = PA_SAMPLE_FLOAT32NE; + oss.channels = 2; + remap.i_ss = &iss; + remap.o_ss = &oss; + remap.map_table_f[0][0] = 1.0; + remap.map_table_f[1][0] = 1.0; + remap_init_func(&remap); + if (!remap.do_remap) { + pa_log_debug("no reference remapping function, abort test"); + return; + } + + remap.do_remap(&remap, stereo_ref, mono, SAMPLES); + remap_mono_to_stereo_neon(&remap, stereo, mono, SAMPLES); + + for (i = 0; i < 2*SAMPLES; i++) { + if (fabsf(stereo[i] - stereo_ref[i]) > 0.00001) { + pa_log_debug("%d: %.3f != %.3f (%.3f)", i, stereo[i], stereo_ref[i], + mono[i/2]); + } + } + + start = pa_rtclock_now(); + for (i = 0; i < TIMES; i++) { + remap_mono_to_stereo_neon(&remap, stereo, mono, SAMPLES); + } + stop = pa_rtclock_now(); + pa_log_info("NEON: %llu usec.", (long long unsigned int)(stop - start)); + + start = pa_rtclock_now(); + for (i = 0; i < TIMES; i++) { + remap.do_remap(&remap, stereo_ref, mono, SAMPLES); + } + stop = pa_rtclock_now(); + pa_log_info("ref: %llu usec.", (long long unsigned int)(stop - start)); +} + +static void run_test_s16_mono_to_stereo(void) { + int16_t stereo[2*SAMPLES]; + int16_t stereo_ref[2*SAMPLES]; + int16_t mono[SAMPLES]; + int i; + pa_usec_t start, stop; + pa_sample_format_t sf; + pa_remap_t remap; + pa_sample_spec iss, oss; + + pa_init_remap_func_t remap_init_func = pa_get_init_remap_func(); + + pa_log_debug("checking NEON remap_mono_to_stereo(s16)"); + + memset(stereo_ref, 0, sizeof(stereo_ref)); + memset(stereo, 0, sizeof(stereo)); + + for (i = 0; i < SAMPLES; i++) { + mono[i] = rand() - RAND_MAX/2; + } + + sf = PA_SAMPLE_S16NE; + remap.format = &sf; + iss.format = PA_SAMPLE_S16NE; + iss.channels = 1; + oss.format = PA_SAMPLE_S16NE; + oss.channels = 2; + remap.i_ss = &iss; + remap.o_ss = &oss; + remap.map_table_f[0][0] = 1.0; + remap.map_table_f[1][0] = 1.0; + remap_init_func(&remap); + if (!remap.do_remap) { + pa_log_debug("no reference remapping function, abort test"); + return; + } + + remap.do_remap(&remap, stereo_ref, mono, SAMPLES); + remap_mono_to_stereo_neon(&remap, stereo, mono, SAMPLES); + + for (i = 0; i < 2*SAMPLES; i++) { + if (abs(stereo[i] - stereo_ref[i]) > 0) { + pa_log_debug("%d: %d != %d (%d)", i, stereo[i], stereo_ref[i], + mono[i/2]); + } + } + + start = pa_rtclock_now(); + for (i = 0; i < TIMES; i++) { + remap_mono_to_stereo_neon(&remap, stereo, mono, SAMPLES); + } + stop = pa_rtclock_now(); + pa_log_info("NEON: %llu usec.", (long long unsigned int)(stop - start)); + + start = pa_rtclock_now(); + for (i = 0; i < TIMES; i++) { + remap.do_remap(&remap, stereo_ref, mono, SAMPLES); + } + stop = pa_rtclock_now(); + pa_log_info("ref: %llu usec.", (long long unsigned int)(stop - start)); +} + +static void run_test_float_stereo_to_mono(void) { + float stereo[2*SAMPLES]; + float mono_ref[SAMPLES]; + float mono[SAMPLES]; + int i; + pa_usec_t start, stop; + pa_sample_format_t sf; + pa_remap_t remap; + pa_sample_spec iss, oss; + + pa_init_remap_func_t remap_init_func = pa_get_init_remap_func(); + + pa_log_debug("checking NEON remap_stereo_to_mono(float)"); + + memset(mono_ref, 0, sizeof(mono_ref)); + memset(mono, 0, sizeof(mono)); + + for (i = 0; i < 2*SAMPLES; i++) { + stereo[i] = rand()/(float) RAND_MAX - 0.5f; + } + + sf = PA_SAMPLE_FLOAT32NE; + remap.format = &sf; + iss.format = PA_SAMPLE_FLOAT32NE; + iss.channels = 2; + oss.format = PA_SAMPLE_FLOAT32NE; + oss.channels = 1; + remap.i_ss = &iss; + remap.o_ss = &oss; + remap.map_table_f[0][0] = 1.0; + remap.map_table_f[0][1] = 1.0; + remap_init_func(&remap); + if (!remap.do_remap) { + pa_log_debug("no reference remapping function, abort test"); + return; + } + + remap.do_remap(&remap, mono_ref, stereo, SAMPLES); + remap_stereo_to_mono_neon(&remap, mono, stereo, SAMPLES); + + for (i = 0; i < SAMPLES; i++) { + if (fabsf(mono[i] - mono_ref[i]) > 0.00001) { + pa_log_debug("%d: %.3f != %.3f (%.3f %.3f)", i, mono[i], mono_ref[i], + stereo[2*i+0], stereo[2*i+1]); + } + } + + start = pa_rtclock_now(); + for (i = 0; i < TIMES; i++) { + remap_stereo_to_mono_neon(&remap, mono, stereo, SAMPLES); + } + stop = pa_rtclock_now(); + pa_log_info("NEON: %llu usec.", (long long unsigned int)(stop - start)); + + start = pa_rtclock_now(); + for (i = 0; i < TIMES; i++) { + remap.do_remap(&remap, mono_ref, stereo, SAMPLES); + } + stop = pa_rtclock_now(); + pa_log_info("ref: %llu usec.", (long long unsigned int)(stop - start)); +} + +static void run_test_s16_stereo_to_mono(void) { + int16_t stereo[2*SAMPLES]; + int16_t mono_ref[SAMPLES]; + int16_t mono[SAMPLES]; + int i; + pa_usec_t start, stop; + pa_sample_format_t sf; + pa_remap_t remap; + pa_sample_spec iss, oss; + + pa_init_remap_func_t remap_init_func = pa_get_init_remap_func(); + + pa_log_debug("checking NEON remap_stereo_to_mono(s16)"); + + memset(mono_ref, 0, sizeof(mono_ref)); + memset(mono, 0, sizeof(mono)); + + for (i = 0; i < 2*SAMPLES; i++) { + stereo[i] = rand() - RAND_MAX/2; + } + + sf = PA_SAMPLE_S16NE; + remap.format = &sf; + iss.format = PA_SAMPLE_S16NE; + iss.channels = 2; + oss.format = PA_SAMPLE_S16NE; + oss.channels = 1; + remap.i_ss = &iss; + remap.o_ss = &oss; + remap.map_table_f[0][0] = 1.0; + remap.map_table_f[0][1] = 1.0; + remap_init_func(&remap); + if (!remap.do_remap) { + pa_log_debug("no reference remapping function, abort test"); + return; + } + + remap.do_remap(&remap, mono_ref, stereo, SAMPLES); + remap_stereo_to_mono_neon(&remap, mono, stereo, SAMPLES); + + for (i = 0; i < SAMPLES; i++) { + if (abs(mono[i] - mono_ref[i]) > 0) { + pa_log_debug("%d: %d != %d (%d %d)", i, mono[i], mono_ref[i], + stereo[2*i+0], stereo[2*i+1]); + } + } + + start = pa_rtclock_now(); + for (i = 0; i < TIMES; i++) { + remap_stereo_to_mono_neon(&remap, mono, stereo, SAMPLES); + } + stop = pa_rtclock_now(); + pa_log_info("NEON: %llu usec.", (long long unsigned int)(stop - start)); + + start = pa_rtclock_now(); + for (i = 0; i < TIMES; i++) { + remap.do_remap(&remap, mono_ref, stereo, SAMPLES); + } + stop = pa_rtclock_now(); + pa_log_info("ref: %llu usec.", (long long unsigned int)(stop - start)); +} +#endif /* RUN_TEST */ + +static void init_remap_neon(pa_remap_t *m) { + unsigned n_oc, n_ic; + + n_oc = m->o_ss->channels; + n_ic = m->i_ss->channels; + + /* find some common channel remappings, fall back to full matrix operation. */ + if (n_ic == 1 && n_oc == 2 && + m->map_table_f[0][0] >= 1.0 && m->map_table_f[1][0] >= 1.0) { + m->do_remap = (pa_do_remap_func_t) remap_mono_to_stereo_neon; + pa_log_info("Using ARM NEON mono to stereo remapping"); + } + else if (n_ic == 2 && n_oc == 1 && + m->map_table_f[0][0] >= 1.0 && m->map_table_f[0][1] >= 1.0) { + m->do_remap = (pa_do_remap_func_t) remap_stereo_to_mono_neon; + pa_log_info("Using ARM NEON stereo to mono remapping"); + } +} +#endif /* defined (__ARM_NEON__) */ + +void pa_remap_func_init_neon(pa_cpu_arm_flag_t flags) { +#if defined (__ARM_NEON__) + +#ifdef RUN_TEST + run_test_float_stereo_to_mono(); + run_test_s16_stereo_to_mono(); + run_test_float_mono_to_stereo(); + run_test_s16_mono_to_stereo(); +#endif + + pa_log_info("Initialising ARM NEON optimized remappers."); + pa_set_init_remap_func((pa_init_remap_func_t) init_remap_neon); + +#endif /* defined (__ARM_NEON__) */ +} -- 1.7.4.1