diff options
| -rw-r--r-- | src/Makefile.am | 8 | ||||
| -rw-r--r-- | src/pulsecore/resampler.c | 568 | ||||
| -rw-r--r-- | src/pulsecore/resampler.h | 8 | ||||
| -rw-r--r-- | src/pulsecore/sink-input.c | 4 | ||||
| -rw-r--r-- | src/pulsecore/source-output.c | 4 | ||||
| -rw-r--r-- | src/tests/remix-test.c | 91 | ||||
| -rw-r--r-- | src/tests/resampler-test.c | 4 | 
7 files changed, 616 insertions, 71 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 8fe7be9d..1a24a529 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -240,7 +240,8 @@ noinst_PROGRAMS = \  		sig2str-test \  		resampler-test \  		smoother-test \ -		mix-test +		mix-test \ +		remix-test  if HAVE_SIGXCPU  noinst_PROGRAMS += \ @@ -395,6 +396,11 @@ mix_test_LDADD = $(AM_LDADD) libpulsecore.la  mix_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)  mix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +remix_test_SOURCES = tests/remix-test.c +remix_test_LDADD = $(AM_LDADD) libpulsecore.la +remix_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) +remix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) +  smoother_test_SOURCES = tests/smoother-test.c  smoother_test_LDADD = $(AM_LDADD) libpulsecore.la  smoother_test_CFLAGS = $(AM_CFLAGS) diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c index eebc9c04..429759f0 100644 --- a/src/pulsecore/resampler.c +++ b/src/pulsecore/resampler.c @@ -38,6 +38,7 @@  #include <pulsecore/sconv.h>  #include <pulsecore/log.h>  #include <pulsecore/macro.h> +#include <pulsecore/strbuf.h>  #include "speexwrap.h" @@ -49,7 +50,9 @@  #define EXTRA_SAMPLES 128  struct pa_resampler { -    pa_resample_method_t resample_method; +    pa_resample_method_t method; +    pa_resample_flags_t flags; +      pa_sample_spec i_ss, o_ss;      pa_channel_map i_cm, o_cm;      size_t i_fz, o_fz, w_sz; @@ -63,8 +66,8 @@ struct pa_resampler {      pa_convert_func_t to_work_format_func;      pa_convert_func_t from_work_format_func; -    int map_table[PA_CHANNELS_MAX][PA_CHANNELS_MAX]; -    int map_required; +    float map_table[PA_CHANNELS_MAX][PA_CHANNELS_MAX]; +    pa_bool_t map_required;      void (*impl_free)(pa_resampler *r);      void (*impl_update_rates)(pa_resampler *r); @@ -159,8 +162,8 @@ pa_resampler* pa_resampler_new(          const pa_channel_map *am,          const pa_sample_spec *b,          const pa_channel_map *bm, -        pa_resample_method_t resample_method, -        int variable_rate) { +        pa_resample_method_t method, +        pa_resample_flags_t flags) {      pa_resampler *r = NULL; @@ -169,37 +172,38 @@ pa_resampler* pa_resampler_new(      pa_assert(b);      pa_assert(pa_sample_spec_valid(a));      pa_assert(pa_sample_spec_valid(b)); -    pa_assert(resample_method >= 0); -    pa_assert(resample_method < PA_RESAMPLER_MAX); +    pa_assert(method >= 0); +    pa_assert(method < PA_RESAMPLER_MAX);      /* Fix method */ -    if (!variable_rate && a->rate == b->rate) { +    if (!(flags & PA_RESAMPLER_VARIABLE_RATE) && a->rate == b->rate) {          pa_log_info("Forcing resampler 'copy', because of fixed, identical sample rates."); -        resample_method = PA_RESAMPLER_COPY; +        method = PA_RESAMPLER_COPY;      } -    if (!pa_resample_method_supported(resample_method)) { -        pa_log_warn("Support for resampler '%s' not compiled in, reverting to 'auto'.", pa_resample_method_to_string(resample_method)); -        resample_method = PA_RESAMPLER_AUTO; +    if (!pa_resample_method_supported(method)) { +        pa_log_warn("Support for resampler '%s' not compiled in, reverting to 'auto'.", pa_resample_method_to_string(method)); +        method = PA_RESAMPLER_AUTO;      } -    if (resample_method == PA_RESAMPLER_FFMPEG && variable_rate) { +    if (method == PA_RESAMPLER_FFMPEG && (flags & PA_RESAMPLER_VARIABLE_RATE)) {          pa_log_info("Resampler 'ffmpeg' cannot do variable rate, reverting to resampler 'auto'."); -        resample_method = PA_RESAMPLER_AUTO; +        method = PA_RESAMPLER_AUTO;      } -    if (resample_method == PA_RESAMPLER_COPY && (variable_rate || a->rate != b->rate)) { +    if (method == PA_RESAMPLER_COPY && ((flags & PA_RESAMPLER_VARIABLE_RATE) || a->rate != b->rate)) {          pa_log_info("Resampler 'copy' cannot change sampling rate, reverting to resampler 'auto'."); -        resample_method = PA_RESAMPLER_AUTO; +        method = PA_RESAMPLER_AUTO;      } -    if (resample_method == PA_RESAMPLER_AUTO) -        resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 3; +    if (method == PA_RESAMPLER_AUTO) +        method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 3;      r = pa_xnew(pa_resampler, 1);      r->mempool = pool; -    r->resample_method = resample_method; +    r->method = method; +    r->flags = flags;      r->impl_free = NULL;      r->impl_update_rates = NULL; @@ -231,12 +235,12 @@ pa_resampler* pa_resampler_new(      calc_map_table(r); -    pa_log_info("Using resampler '%s'", pa_resample_method_to_string(resample_method)); +    pa_log_info("Using resampler '%s'", pa_resample_method_to_string(method)); -    if ((resample_method >= PA_RESAMPLER_SPEEX_FIXED_BASE && resample_method <= PA_RESAMPLER_SPEEX_FIXED_MAX) || -        (resample_method == PA_RESAMPLER_FFMPEG)) +    if ((method >= PA_RESAMPLER_SPEEX_FIXED_BASE && method <= PA_RESAMPLER_SPEEX_FIXED_MAX) || +        (method == PA_RESAMPLER_FFMPEG))          r->work_format = PA_SAMPLE_S16NE; -    else if (resample_method == PA_RESAMPLER_TRIVIAL || resample_method == PA_RESAMPLER_COPY) { +    else if (method == PA_RESAMPLER_TRIVIAL || method == PA_RESAMPLER_COPY) {          if (r->map_required || a->format != b->format) { @@ -281,7 +285,7 @@ pa_resampler* pa_resampler_new(      }      /* initialize implementation */ -    if (init_table[resample_method](r) < 0) +    if (init_table[method](r) < 0)          goto fail;      return r; @@ -373,7 +377,7 @@ size_t pa_resampler_max_block_size(pa_resampler *r) {  pa_resample_method_t pa_resampler_get_method(pa_resampler *r) {      pa_assert(r); -    return r->resample_method; +    return r->method;  }  static const char * const resample_methods[] = { @@ -449,36 +453,423 @@ pa_resample_method_t pa_parse_resample_method(const char *string) {      return PA_RESAMPLER_INVALID;  } +static pa_bool_t on_left(pa_channel_position_t p) { + +    return +        p == PA_CHANNEL_POSITION_FRONT_LEFT || +        p == PA_CHANNEL_POSITION_REAR_LEFT || +        p == PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER || +        p == PA_CHANNEL_POSITION_SIDE_LEFT || +        p == PA_CHANNEL_POSITION_TOP_FRONT_LEFT || +        p == PA_CHANNEL_POSITION_TOP_REAR_LEFT; +} + +static pa_bool_t on_right(pa_channel_position_t p) { + +    return +        p == PA_CHANNEL_POSITION_FRONT_RIGHT || +        p == PA_CHANNEL_POSITION_REAR_RIGHT || +        p == PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER || +        p == PA_CHANNEL_POSITION_SIDE_RIGHT || +        p == PA_CHANNEL_POSITION_TOP_FRONT_RIGHT || +        p == PA_CHANNEL_POSITION_TOP_REAR_RIGHT; +} + +static pa_bool_t on_center(pa_channel_position_t p) { + +    return +        p == PA_CHANNEL_POSITION_FRONT_CENTER || +        p == PA_CHANNEL_POSITION_REAR_CENTER || +        p == PA_CHANNEL_POSITION_TOP_CENTER || +        p == PA_CHANNEL_POSITION_TOP_FRONT_CENTER || +        p == PA_CHANNEL_POSITION_TOP_REAR_CENTER; +} + +static pa_bool_t on_lfe(pa_channel_position_t p) { +    return +        p == PA_CHANNEL_POSITION_LFE; +} +  static void calc_map_table(pa_resampler *r) { -    unsigned oc; +    unsigned oc, ic; +    pa_bool_t ic_connected[PA_CHANNELS_MAX]; +    pa_bool_t remix; +    pa_strbuf *s; +    char *t;      pa_assert(r); -    if (!(r->map_required = (r->i_ss.channels != r->o_ss.channels || !pa_channel_map_equal(&r->i_cm, &r->o_cm)))) +    if (!(r->map_required = (r->i_ss.channels != r->o_ss.channels || (!(r->flags & PA_RESAMPLER_NO_REMAP) && !pa_channel_map_equal(&r->i_cm, &r->o_cm)))))          return; +    memset(r->map_table, 0, sizeof(r->map_table)); +    memset(ic_connected, 0, sizeof(ic_connected)); +    remix = (r->flags & (PA_RESAMPLER_NO_REMAP|PA_RESAMPLER_NO_REMIX)) == 0; +      for (oc = 0; oc < r->o_ss.channels; oc++) { -        unsigned ic, i = 0; +        pa_bool_t oc_connected = FALSE; +        pa_channel_position_t b = r->o_cm.map[oc];          for (ic = 0; ic < r->i_ss.channels; ic++) { -            pa_channel_position_t a, b; +            pa_channel_position_t a = r->i_cm.map[ic]; + +            if (r->flags & PA_RESAMPLER_NO_REMAP) { +                /* We shall not do any remapping. Hence, just check by index */ + +                if (ic == oc) +                    r->map_table[oc][ic] = 1.0; + +                continue; +            } + +            if (r->flags & PA_RESAMPLER_NO_REMIX) { +                /* We shall not do any remixing. Hence, just check by name */ + +                if (a == b) +                    r->map_table[oc][ic] = 1.0; + +                continue; +            } + +            pa_assert(remix); + +            /* OK, we shall do the full monty: upmixing and +             * downmixing. Our algorithm is relatively simple, does +             * not do spacialization, delay elements or apply lowpass +             * filters for LFE. Patches are always welcome, +             * though. Oh, and it doesn't do any matrix +             * decoding. (Which probably wouldn't make any sense +             * anyway.) +             * +             * This code is not idempotent: downmixing an upmixed +             * stereo stream is not identical to the original. The +             * volume will not match, and the two channels will be a +             * linear combination of both. +             * +             * This is losely based on random suggestions found on the +             * Internet, such as this: +             * http://www.halfgaar.net/surround-sound-in-linux and the +             * alsa upmix plugin. +             * +             * The algorithm works basically like this: +             * +             * 1) Connect all channels with matching names. +             * +             * 2) Mono Handling: +             *    S:Mono: Copy into all D:channels +             *    D:Mono: Copy in all S:channels +             * +             * 3) Mix D:Left, D:Right: +             *    D:Left: If not connected, avg all S:Left +             *    D:Right: If not connected, avg all S:Right +             * +             * 4) Mix D:Center +             *       If not connected, avg all S:Center +             *       If still not connected, avg all S:Left, S:Right +             * +             * 5) Mix D:LFE +             *       If not connected, avg all S:* +             * +             * 6) Make sure S:Left/S:Right is used: S:Left/S:Right: If +             *    not connected, mix into all D:left and all D:right +             *    channels. Gain is 0.1, the current left and right +             *    should be multiplied by 0.9. +             * +             * 7) Make sure S:Center, S:LFE is used: +             * +             *    S:Center, S:LFE: If not connected, mix into all +             *    D:left, all D:right, all D:center channels, gain is +             *    0.375. The current (as result of 1..6) factors +             *    should be multiplied by 0.75. (Alt. suggestion: 0.25 +             *    vs. 0.5) +             * +             * S: and D: shall relate to the source resp. destination channels. +             * +             * Rationale: 1, 2 are probably obvious. For 3: this +             * copies front to rear if needed. For 4: we try to find +             * some suitable C source for C, if we don't find any, we +             * avg L and R. For 5: LFE is mixed from all channels. For +             * 6: the rear channels should not be dropped entirely, +             * however have only minimal impact. For 7: movies usually +             * encode speech on the center channel. Thus we have to +             * make sure this channel is distributed to L and R if not +             * available in the output. Also, LFE is used to achieve a +             * greater dynamic range, and thus we should try to do our +             * best to pass it to L+R. +             */ + +            if (a == b || a == PA_CHANNEL_POSITION_MONO || b == PA_CHANNEL_POSITION_MONO) { +                r->map_table[oc][ic] = 1.0; + +                oc_connected = TRUE; +                ic_connected[ic] = TRUE; +            } +        } + +        if (!oc_connected && remix) { +            /* OK, we shall remix */ + +            if (on_left(b)) { +                unsigned n = 0; + +                /* We are not connected and on the left side, let's +                 * average all left side input channels. */ + +                for (ic = 0; ic < r->i_ss.channels; ic++) +                    if (on_left(r->i_cm.map[ic])) +                        n++; + +                if (n > 0) +                    for (ic = 0; ic < r->i_ss.channels; ic++) +                        if (on_left(r->i_cm.map[ic])) { +                            r->map_table[oc][ic] = 1.0 / n; +                            ic_connected[ic] = TRUE; +                        } + +                /* We ignore the case where there is no left input +                 * channel. Something is really wrong in this case +                 * anyway. */ + +            } else if (on_right(b)) { +                unsigned n = 0; + +                /* We are not connected and on the right side, let's +                 * average all right side input channels. */ + +                for (ic = 0; ic < r->i_ss.channels; ic++) +                    if (on_right(r->i_cm.map[ic])) +                        n++; + +                if (n > 0) +                    for (ic = 0; ic < r->i_ss.channels; ic++) +                        if (on_right(r->i_cm.map[ic])) { +                            r->map_table[oc][ic] = 1.0 / n; +                            ic_connected[ic] = TRUE; +                        } + +                /* We ignore the case where there is no right input +                 * channel. Something is really wrong in this case +                 * anyway. */ + +            } else if (on_center(b)) { +                unsigned n = 0; + +                /* We are not connected and at the center. Let's +                 * average all center input channels. */ + +                for (ic = 0; ic < r->i_ss.channels; ic++) +                    if (on_center(r->i_cm.map[ic])) +                        n++; + +                if (n > 0) { +                    for (ic = 0; ic < r->i_ss.channels; ic++) +                        if (on_center(r->i_cm.map[ic])) { +                            r->map_table[oc][ic] = 1.0 / n; +                            ic_connected[ic] = TRUE; +                        } +                } else { + +                    /* Hmm, no center channel around, let's synthesize +                     * it by mixing L and R.*/ + +                    n = 0; + +                    for (ic = 0; ic < r->i_ss.channels; ic++) +                        if (on_left(r->i_cm.map[ic]) || on_right(r->i_cm.map[ic])) +                            n++; + +                    if (n > 0) +                        for (ic = 0; ic < r->i_ss.channels; ic++) +                            if (on_left(r->i_cm.map[ic]) || on_right(r->i_cm.map[ic])) { +                                r->map_table[oc][ic] = 1.0 / n; +                                ic_connected[ic] = TRUE; +                            } + +                    /* We ignore the case where there is not even a +                     * left or right input channel. Something is +                     * really wrong in this case anyway. */ +                } + +            } else if (on_lfe(b)) { + +                /* We are not connected and an LFE. Let's average all +                 * channels for LFE. */ + +                for (ic = 0; ic < r->i_ss.channels; ic++) { +                    r->map_table[oc][ic] = 1.0 / r->i_ss.channels; + +                    /* Please note that a channel connected to LFE +                     * doesn't really count as connected. */ +                } +            } +        } +    } + +    if (remix) { +        unsigned +            ic_unconnected_left = 0, +            ic_unconnected_right = 0, +            ic_unconnected_center = 0, +            ic_unconnected_lfe = 0; + +        for (ic = 0; ic < r->i_ss.channels; ic++) { +            pa_channel_position_t a = r->i_cm.map[ic]; + +            if (ic_connected[ic]) +                continue; + +            if (on_left(a)) +                ic_unconnected_left++; +            else if (on_right(a)) +                ic_unconnected_right++; +            else if (on_center(a)) +                ic_unconnected_center++; +            else if (on_lfe(a)) +                ic_unconnected_lfe++; +        } + +        if (ic_unconnected_left > 0) { + +            /* OK, so there are unconnected input channels on the +             * left. Let's multiply all already connected channels on +             * the left side by .9 and add in our averaged unconnected +             * channels multplied by .1 */ + +            for (oc = 0; oc < r->o_ss.channels; oc++) { + +                if (!on_left(r->o_cm.map[oc])) +                    continue; + +                for (ic = 0; ic < r->i_ss.channels; ic++) { + +                    if (ic_connected[ic]) { +                        r->map_table[oc][ic] *= .9; +                        continue; +                    } -            a = r->i_cm.map[ic]; -            b = r->o_cm.map[oc]; +                    if (on_left(r->i_cm.map[ic])) +                        r->map_table[oc][ic] = .1 / ic_unconnected_left; +                } +            } +        } + +        if (ic_unconnected_right > 0) { + +            /* OK, so there are unconnected input channels on the +             * right. Let's multiply all already connected channels on +             * the right side by .9 and add in our averaged unconnected +             * channels multplied by .1 */ + +            for (oc = 0; oc < r->o_ss.channels; oc++) { + +                if (!on_right(r->o_cm.map[oc])) +                    continue; + +                for (ic = 0; ic < r->i_ss.channels; ic++) { + +                    if (ic_connected[ic]) { +                        r->map_table[oc][ic] *= .9; +                        continue; +                    } + +                    if (on_right(r->i_cm.map[ic])) +                        r->map_table[oc][ic] = .1 / ic_unconnected_right; +                } +            } +        } + +        if (ic_unconnected_center > 0) { +            pa_bool_t mixed_in = FALSE; + +            /* OK, so there are unconnected input channels on the +             * center. Let's multiply all already connected channels on +             * the center side by .9 and add in our averaged unconnected +             * channels multplied by .1 */ + +            for (oc = 0; oc < r->o_ss.channels; oc++) { -            if (a == b || -                (a == PA_CHANNEL_POSITION_MONO && b == PA_CHANNEL_POSITION_LEFT) || -                (a == PA_CHANNEL_POSITION_MONO && b == PA_CHANNEL_POSITION_RIGHT) || -                (a == PA_CHANNEL_POSITION_LEFT && b == PA_CHANNEL_POSITION_MONO) || -                (a == PA_CHANNEL_POSITION_RIGHT && b == PA_CHANNEL_POSITION_MONO)) +                if (!on_center(r->o_cm.map[oc])) +                    continue; -                r->map_table[oc][i++] = ic; +                for (ic = 0; ic < r->i_ss.channels; ic++)  { + +                    if (ic_connected[ic]) { +                        r->map_table[oc][ic] *= .9; +                        continue; +                    } + +                    if (on_center(r->i_cm.map[ic])) { +                        r->map_table[oc][ic] = .1 / ic_unconnected_center; +                        mixed_in = TRUE; +                    } +                } +            } + +            if (!mixed_in) { + +                /* Hmm, as it appears there was no center channel we +                   could mix our center channel in. In this case, mix +                   it into left and right. Using .375 and 0.75 as +                   factors. */ + +                for (oc = 0; oc < r->o_ss.channels; oc++) { + +                    if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc])) +                        continue; + +                    for (ic = 0; ic < r->i_ss.channels; ic++)  { + +                        if (ic_connected[ic]) { +                            r->map_table[oc][ic] *= .75; +                            continue; +                        } + +                        if (on_center(r->i_cm.map[ic])) +                            r->map_table[oc][ic] = .375 / ic_unconnected_center; +                    } +                } +            }          } -        /* Add an end marker */ -        if (i < PA_CHANNELS_MAX) -            r->map_table[oc][i] = -1; +        if (ic_unconnected_lfe > 0) { + +            /* OK, so there is an unconnected LFE channel. Let's mix +             * it into all channels, with factor 0.375 */ + +            for (ic = 0; ic < r->i_ss.channels; ic++)  { + +                if (!on_lfe(r->i_cm.map[ic])) +                    continue; + +                for (oc = 0; oc < r->o_ss.channels; oc++) +                    r->map_table[oc][ic] = 0.375 / ic_unconnected_lfe; +            } +        } +    } + + +    s = pa_strbuf_new(); + +    pa_strbuf_printf(s, "     "); +    for (ic = 0; ic < r->i_ss.channels; ic++) +        pa_strbuf_printf(s, "  I%02u ", ic); +    pa_strbuf_puts(s, "\n    +"); + +    for (ic = 0; ic < r->i_ss.channels; ic++) +        pa_strbuf_printf(s, "------"); +    pa_strbuf_puts(s, "\n"); + +    for (oc = 0; oc < r->o_ss.channels; oc++) { +        pa_strbuf_printf(s, "O%02u |", oc); + +        for (ic = 0; ic < r->i_ss.channels; ic++) +            pa_strbuf_printf(s, " %1.3f", r->map_table[oc][ic]); + +        pa_strbuf_puts(s, "\n");      } + +    pa_log_debug("Channel matrix:\n%s", t = pa_strbuf_tostring_free(s)); +    pa_xfree(t);  }  static pa_memchunk* convert_to_work_format(pa_resampler *r, pa_memchunk *input) { @@ -518,6 +909,36 @@ static pa_memchunk* convert_to_work_format(pa_resampler *r, pa_memchunk *input)      return &r->buf1;  } +static void vectoradd_s16_with_fraction( +        int16_t *d, int dstr, +        const int16_t *s1, int sstr1, +        const int16_t *s2, int sstr2, +        int n, +        float s3, float s4) { + +    int32_t i3, i4; + +    i3 = (int32_t) (s3 * 0x10000); +    i4 = (int32_t) (s4 * 0x10000); + +    for (; n > 0; n--) { +        int32_t a, b; + +        a = *s1; +        b = *s2; + +        a = (a * i3) / 0x10000; +        b = (b * i4) / 0x10000; + +        *d = (int16_t) (a + b); + +        s1 = (const int16_t*) ((const uint8_t*) s1 + sstr1); +        s2 = (const int16_t*) ((const uint8_t*) s2 + sstr2); +        d = (int16_t*) ((uint8_t*) d + dstr); + +    } +} +  static pa_memchunk *remap_channels(pa_resampler *r, pa_memchunk *input) {      unsigned in_n_samples, out_n_samples, n_frames;      int i_skip, o_skip; @@ -560,16 +981,21 @@ static pa_memchunk *remap_channels(pa_resampler *r, pa_memchunk *input) {          case PA_SAMPLE_FLOAT32NE:              for (oc = 0; oc < r->o_ss.channels; oc++) { -                unsigned i; +                unsigned ic;                  static const float one = 1.0; -                for (i = 0; i < PA_CHANNELS_MAX && r->map_table[oc][i] >= 0; i++) +                for (ic = 0; ic < r->i_ss.channels; ic++) { + +                    if (r->map_table[oc][ic] <= 0.0) +                        continue; +                      oil_vectoradd_f32(                              (float*) dst + oc, o_skip,                              (float*) dst + oc, o_skip, -                            (float*) src + r->map_table[oc][i], i_skip, +                            (float*) src + ic, i_skip,                              n_frames, -                            &one, &one); +                            &one, &r->map_table[oc][ic]); +                }              }              break; @@ -577,16 +1003,32 @@ static pa_memchunk *remap_channels(pa_resampler *r, pa_memchunk *input) {          case PA_SAMPLE_S16NE:              for (oc = 0; oc < r->o_ss.channels; oc++) { -                unsigned i; -                static const int16_t one = 1; - -                for (i = 0; i < PA_CHANNELS_MAX && r->map_table[oc][i] >= 0; i++) -                    oil_vectoradd_s16( -                            (int16_t*) dst + oc, o_skip, -                            (int16_t*) dst + oc, o_skip, -                            (int16_t*) src + r->map_table[oc][i], i_skip, -                            n_frames, -                            &one, &one); +                unsigned ic; + +                for (ic = 0; ic < r->i_ss.channels; ic++) { + +                    if (r->map_table[oc][ic] <= 0.0) +                        continue; + +                    if (r->map_table[oc][ic] >= 1.0) { +                        static const int16_t one = 1; + +                        oil_vectoradd_s16( +                                (int16_t*) dst + oc, o_skip, +                                (int16_t*) dst + oc, o_skip, +                                (int16_t*) src + ic, i_skip, +                                n_frames, +                                &one, &one); + +                    } else + +                        vectoradd_s16_with_fraction( +                                (int16_t*) dst + oc, o_skip, +                                (int16_t*) dst + oc, o_skip, +                                (int16_t*) src + ic, i_skip, +                                n_frames, +                                1.0, r->map_table[oc][ic]); +                }              }              break; @@ -751,7 +1193,7 @@ static int libsamplerate_init(pa_resampler *r) {      pa_assert(r); -    if (!(r->src.state = src_new(r->resample_method, r->o_ss.channels, &err))) +    if (!(r->src.state = src_new(r->method, r->o_ss.channels, &err)))          return -1;      r->impl_free = libsamplerate_free; @@ -809,10 +1251,10 @@ static void speex_resample_int(pa_resampler *r, const pa_memchunk *input, unsign  static void speex_update_rates(pa_resampler *r) {      pa_assert(r); -    if (r->resample_method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->resample_method <= PA_RESAMPLER_SPEEX_FIXED_MAX) +    if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX)          pa_assert_se(paspfx_resampler_set_rate(r->speex.state, r->i_ss.rate, r->o_ss.rate) == 0);      else { -        pa_assert(r->resample_method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->resample_method <= PA_RESAMPLER_SPEEX_FLOAT_MAX); +        pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX);          pa_assert_se(paspfl_resampler_set_rate(r->speex.state, r->i_ss.rate, r->o_ss.rate) == 0);      }  } @@ -823,10 +1265,10 @@ static void speex_free(pa_resampler *r) {      if (!r->speex.state)          return; -    if (r->resample_method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->resample_method <= PA_RESAMPLER_SPEEX_FIXED_MAX) +    if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX)          paspfx_resampler_destroy(r->speex.state);      else { -        pa_assert(r->resample_method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->resample_method <= PA_RESAMPLER_SPEEX_FLOAT_MAX); +        pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX);          paspfl_resampler_destroy(r->speex.state);      }  } @@ -839,8 +1281,8 @@ static int speex_init(pa_resampler *r) {      r->impl_free = speex_free;      r->impl_update_rates = speex_update_rates; -    if (r->resample_method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->resample_method <= PA_RESAMPLER_SPEEX_FIXED_MAX) { -        q = r->resample_method - PA_RESAMPLER_SPEEX_FIXED_BASE; +    if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX) { +        q = r->method - PA_RESAMPLER_SPEEX_FIXED_BASE;          pa_log_info("Choosing speex quality setting %i.", q); @@ -849,8 +1291,8 @@ static int speex_init(pa_resampler *r) {          r->impl_resample = speex_resample_int;      } else { -        pa_assert(r->resample_method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->resample_method <= PA_RESAMPLER_SPEEX_FLOAT_MAX); -        q = r->resample_method - PA_RESAMPLER_SPEEX_FLOAT_BASE; +        pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX); +        q = r->method - PA_RESAMPLER_SPEEX_FLOAT_BASE;          pa_log_info("Choosing speex quality setting %i.", q); diff --git a/src/pulsecore/resampler.h b/src/pulsecore/resampler.h index 23e1acb7..778c738d 100644 --- a/src/pulsecore/resampler.h +++ b/src/pulsecore/resampler.h @@ -49,6 +49,12 @@ typedef enum pa_resample_method {      PA_RESAMPLER_MAX  } pa_resample_method_t; +typedef enum pa_resample_flags { +    PA_RESAMPLER_VARIABLE_RATE = 1, +    PA_RESAMPLER_NO_REMAP = 2,  /* implies NO_REMIX */ +    PA_RESAMPLER_NO_REMIX = 4 +} pa_resample_flags_t; +  pa_resampler* pa_resampler_new(          pa_mempool *pool,          const pa_sample_spec *a, @@ -56,7 +62,7 @@ pa_resampler* pa_resampler_new(          const pa_sample_spec *b,          const pa_channel_map *bm,          pa_resample_method_t resample_method, -        int variable_rate); +        pa_resample_flags_t flags);  void pa_resampler_free(pa_resampler *r); diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 6f654b61..730f3e5c 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -154,7 +154,7 @@ pa_sink_input* pa_sink_input_new(                        &data->sample_spec, &data->channel_map,                        &data->sink->sample_spec, &data->sink->channel_map,                        data->resample_method, -                      !!(flags & PA_SINK_INPUT_VARIABLE_RATE)))) { +                      (flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0))) {              pa_log_warn("Unsupported resampling operation.");              return NULL;          } @@ -750,7 +750,7 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately) {                        &i->sample_spec, &i->channel_map,                        &dest->sample_spec, &dest->channel_map,                        i->resample_method, -                      !!(i->flags & PA_SINK_INPUT_VARIABLE_RATE)))) { +                      (i->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0))) {              pa_log_warn("Unsupported resampling operation.");              return -1;          } diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 2a902dc2..5c52419e 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -122,7 +122,7 @@ pa_source_output* pa_source_output_new(                        &data->source->sample_spec, &data->source->channel_map,                        &data->sample_spec, &data->channel_map,                        data->resample_method, -                      !!(flags & PA_SOURCE_OUTPUT_VARIABLE_RATE)))) { +                      (flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0))) {              pa_log_warn("Unsupported resampling operation.");              return NULL;          } @@ -415,7 +415,7 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest) {                        &dest->sample_spec, &dest->channel_map,                        &o->sample_spec, &o->channel_map,                        o->resample_method, -                      !!(o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE)))) { +                      (o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0))) {              pa_log_warn("Unsupported resampling operation.");              return -1;          } diff --git a/src/tests/remix-test.c b/src/tests/remix-test.c new file mode 100644 index 00000000..d2fa6943 --- /dev/null +++ b/src/tests/remix-test.c @@ -0,0 +1,91 @@ +/* $Id$ */ + +/*** +  This file is part of PulseAudio. + +  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 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 <stdio.h> + +#include <pulse/sample.h> +#include <pulse/volume.h> + +#include <pulsecore/resampler.h> +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> +#include <pulsecore/memblock.h> +#include <pulsecore/sample-util.h> + +#include <liboil/liboil.h> + +int main(int argc, char *argv[]) { + +    static const pa_channel_map maps[] = { +        { 1, { PA_CHANNEL_POSITION_MONO } }, +        { 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } }, +        { 3, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_CENTER } }, +        { 3, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_LFE } }, +        { 3, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_CENTER } }, +        { 4, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE } }, +        { 4, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_REAR_CENTER } }, +        { 4, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT } }, +        { 5, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_CENTER } }, +        { 5, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE } }, +        { 6, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_CENTER } }, +        { 8, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT } }, +        { 0, { 0 } } +    }; + +    unsigned i, j; +    pa_mempool *pool; + +    oil_init(); +    pa_log_set_maximal_level(PA_LOG_DEBUG); + +    pa_assert_se(pool = pa_mempool_new(FALSE)); + +    for (i = 0; maps[i].channels > 0; i++) +        for (j = 0; maps[j].channels > 0; j++) { +            char a[PA_CHANNEL_MAP_SNPRINT_MAX], b[PA_CHANNEL_MAP_SNPRINT_MAX]; +            pa_resampler *r; +            pa_sample_spec ss1, ss2; + +            pa_log_info("Converting from '%s' to '%s'.\n", pa_channel_map_snprint(a, sizeof(a), &maps[i]), pa_channel_map_snprint(b, sizeof(b), &maps[j])); + +            ss1.channels = maps[i].channels; +            ss2.channels = maps[j].channels; + +            ss1.rate = ss2.rate = 44100; +            ss1.format = ss2.format = PA_SAMPLE_S16NE; + +            r = pa_resampler_new(pool, &ss1, &maps[i], &ss2, &maps[j], PA_RESAMPLER_AUTO, 0); + +            /* We don't really care for the resampler. We just want to +             * see the remixing debug output. */ + +            pa_resampler_free(r); +        } + + +    pa_mempool_free(pool); + +    return 0; +} diff --git a/src/tests/resampler-test.c b/src/tests/resampler-test.c index 5295995b..820a0c1e 100644 --- a/src/tests/resampler-test.c +++ b/src/tests/resampler-test.c @@ -222,8 +222,8 @@ int main(int argc, char *argv[]) {                     pa_sample_format_to_string(b.format),                     pa_sample_format_to_string(a.format)); -            pa_assert_se(forth = pa_resampler_new(pool, &a, NULL, &b, NULL, PA_RESAMPLER_AUTO, FALSE)); -            pa_assert_se(back = pa_resampler_new(pool, &b, NULL, &a, NULL, PA_RESAMPLER_AUTO, FALSE)); +            pa_assert_se(forth = pa_resampler_new(pool, &a, NULL, &b, NULL, PA_RESAMPLER_AUTO, 0)); +            pa_assert_se(back = pa_resampler_new(pool, &b, NULL, &a, NULL, PA_RESAMPLER_AUTO, 0));              i.memblock = generate_block(pool, &a);              i.length = pa_memblock_get_length(i.memblock);  | 
