]> code.delx.au - pulseaudio/blobdiff - src/pulsecore/resampler.c
remap: Refactor channel remap table computation calc_map_table()
[pulseaudio] / src / pulsecore / resampler.c
index 638b53f7f14070ae45e3c71b4580b48ae6348d92..cbe5e522c527ef4ad82b8924b9046acefb7a1a5c 100644 (file)
@@ -65,13 +65,14 @@ struct pa_resampler {
     size_t resample_buf_size;
     size_t from_work_format_buf_size;
 
-    /* points to buffer before resampling stage, remap */
+    /* points to buffer before resampling stage, remap or to_work */
     pa_memchunk *leftover_buf;
     size_t *leftover_buf_size;
 
-    /* have_leftover points to leftover_in_remap */
+    /* have_leftover points to leftover_in_remap or leftover_in_to_work */
     bool *have_leftover;
     bool leftover_in_remap;
+    bool leftover_in_to_work;
 
     pa_sample_format_t work_format;
     uint8_t work_channels;
@@ -100,7 +101,6 @@ struct peaks_data { /* data specific to the peak finder pseudo resampler */
 
 struct ffmpeg_data { /* data specific to ffmpeg */
     struct AVResampleContext *state;
-    pa_memchunk buf[PA_CHANNELS_MAX];
 };
 
 static int copy_init(pa_resampler *r);
@@ -114,7 +114,7 @@ static int peaks_init(pa_resampler*r);
 static int libsamplerate_init(pa_resampler*r);
 #endif
 
-static void calc_map_table(pa_resampler *r);
+static void calc_map_table(const pa_resampler *r, pa_remap_t *m);
 
 static int (* const init_table[])(pa_resampler*r) = {
 #ifdef HAVE_LIBSAMPLERATE
@@ -203,8 +203,8 @@ static pa_resample_method_t pa_resampler_fix_method(
                 const uint32_t rate_a,
                 const uint32_t rate_b) {
 
-    pa_assert(rate_a > 0 && rate_a <= PA_RATE_MAX);
-    pa_assert(rate_b > 0 && rate_b <= PA_RATE_MAX);
+    pa_assert(pa_sample_rate_valid(rate_a));
+    pa_assert(pa_sample_rate_valid(rate_b));
     pa_assert(method >= 0);
     pa_assert(method < PA_RESAMPLER_MAX);
 
@@ -254,8 +254,8 @@ static pa_resample_method_t pa_resampler_fix_method(
 
 /* Return true if a is a more precise sample format than b, else return false */
 static bool sample_format_more_precise(pa_sample_format_t a, pa_sample_format_t b) {
-    pa_assert(a >= 0 && a < PA_SAMPLE_MAX);
-    pa_assert(b >= 0 && b < PA_SAMPLE_MAX);
+    pa_assert(pa_sample_format_valid(a));
+    pa_assert(pa_sample_format_valid(b));
 
     switch (a) {
         case PA_SAMPLE_U8:
@@ -306,8 +306,8 @@ static pa_sample_format_t pa_resampler_choose_work_format(
                     bool map_required) {
     pa_sample_format_t work_format;
 
-    pa_assert(a >= 0 && a < PA_SAMPLE_MAX);
-    pa_assert(b >= 0 && b < PA_SAMPLE_MAX);
+    pa_assert(pa_sample_format_valid(a));
+    pa_assert(pa_sample_format_valid(b));
     pa_assert(method >= 0);
     pa_assert(method < PA_RESAMPLER_MAX);
 
@@ -396,14 +396,12 @@ pa_resampler* pa_resampler_new(
     r->i_fz = pa_frame_size(a);
     r->o_fz = pa_frame_size(b);
 
-    calc_map_table(r);
-
-    pa_log_info("Using resampler '%s'", pa_resample_method_to_string(method));
+    /* compute channel remap table if needed */
+    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)))))
+        calc_map_table(r, &r->remap);
 
     r->work_format = pa_resampler_choose_work_format(method, a->format, b->format, r->map_required);
-
-    pa_log_info("Using %s as working format.", pa_sample_format_to_string(r->work_format));
-
     r->w_sz = pa_sample_size_of_format(r->work_format);
 
     if (r->i_ss.format != r->work_format) {
@@ -428,14 +426,31 @@ pa_resampler* pa_resampler_new(
         }
     }
 
-    /* leftover buffer is the buffer before the resampling stage */
-    r->leftover_buf = &r->remap_buf;
-    r->leftover_buf_size = &r->remap_buf_size;
-    r->have_leftover = &r->leftover_in_remap;
+    if (r->o_ss.channels <= r->i_ss.channels) {
+        /* pipeline is: format conv. -> remap -> resample -> format conv. */
+        r->work_channels = r->o_ss.channels;
+
+        /* leftover buffer is remap output buffer (before resampling) */
+        r->leftover_buf = &r->remap_buf;
+        r->leftover_buf_size = &r->remap_buf_size;
+        r->have_leftover = &r->leftover_in_remap;
+    } else {
+        /* pipeline is: format conv. -> resample -> remap -> format conv. */
+        r->work_channels = r->i_ss.channels;
 
-    r->work_channels = r->o_ss.channels;
+        /* leftover buffer is to_work output buffer (before resampling) */
+        r->leftover_buf = &r->to_work_format_buf;
+        r->leftover_buf_size = &r->to_work_format_buf_size;
+        r->have_leftover = &r->leftover_in_to_work;
+    }
     r->w_fz = pa_sample_size_of_format(r->work_format) * r->work_channels;
 
+    pa_log_debug("Resampler:");
+    pa_log_debug("  rate %d -> %d (method %s)", a->rate, b->rate, pa_resample_method_to_string(r->method));
+    pa_log_debug("  format %s -> %s (intermediate %s)", pa_sample_format_to_string(a->format),
+                 pa_sample_format_to_string(b->format), pa_sample_format_to_string(r->work_format));
+    pa_log_debug("  channels %d -> %d (resampling %d)", a->channels, b->channels, r->work_channels);
+
     /* initialize implementation */
     if (init_table[method](r) < 0)
         goto fail;
@@ -773,21 +788,16 @@ static int front_rear_side(pa_channel_position_t p) {
     return ON_OTHER;
 }
 
-static void calc_map_table(pa_resampler *r) {
+static void calc_map_table(const pa_resampler *r, pa_remap_t *m) {
     unsigned oc, ic;
     unsigned n_oc, n_ic;
     bool ic_connected[PA_CHANNELS_MAX];
     bool remix;
     pa_strbuf *s;
     char *t;
-    pa_remap_t *m;
 
     pa_assert(r);
-
-    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;
-
-    m = &r->remap;
+    pa_assert(m);
 
     n_oc = r->o_ss.channels;
     n_ic = r->i_ss.channels;
@@ -1138,44 +1148,75 @@ static void calc_map_table(pa_resampler *r) {
     pa_xfree(t);
 
     /* initialize the remapping function */
-    pa_init_remap(m);
+    pa_init_remap_func(m);
 }
 
-static size_t fit_buf(pa_resampler *r, pa_memchunk *buf, size_t size) {
-    if (!buf->memblock || size < buf->length) {
-        size = buf->length;
-        if (buf->memblock)
+/* check if buf's memblock is large enough to hold 'len' bytes; create a
+ * new memblock if necessary and optionally preserve 'copy' data bytes */
+static void fit_buf(pa_resampler *r, pa_memchunk *buf, size_t len, size_t *size, size_t copy) {
+    pa_assert(size);
+
+    if (!buf->memblock || len > *size) {
+        pa_memblock *new_block = pa_memblock_new(r->mempool, len);
+
+        if (buf->memblock) {
+            if (copy > 0) {
+                void *src = pa_memblock_acquire(buf->memblock);
+                void *dst = pa_memblock_acquire(new_block);
+                pa_assert(copy <= len);
+                memcpy(dst, src, copy);
+                pa_memblock_release(new_block);
+                pa_memblock_release(buf->memblock);
+            }
+
             pa_memblock_unref(buf->memblock);
+        }
 
-        buf->memblock = pa_memblock_new(r->mempool, size);
+        buf->memblock = new_block;
+        *size = len;
     }
-    return size;
+
+    buf->length = len;
 }
 
 static pa_memchunk* convert_to_work_format(pa_resampler *r, pa_memchunk *input) {
-    unsigned n_samples;
+    unsigned in_n_samples, out_n_samples;
     void *src, *dst;
+    bool have_leftover;
+    size_t leftover_length = 0;
 
     pa_assert(r);
     pa_assert(input);
     pa_assert(input->memblock);
 
     /* Convert the incoming sample into the work sample format and place them
-     * in to_work_format_buf. */
+     * in to_work_format_buf. The leftover data is already converted, so it's
+     * part of the output buffer. */
+
+    have_leftover = r->leftover_in_to_work;
+    r->leftover_in_to_work = false;
 
-    if (!r->to_work_format_func || !input->length)
+    if (!have_leftover && (!r->to_work_format_func || !input->length))
         return input;
+    else if (input->length <= 0)
+        return &r->to_work_format_buf;
 
-    n_samples = (unsigned) ((input->length / r->i_fz) * r->i_ss.channels);
+    in_n_samples = out_n_samples = (unsigned) ((input->length / r->i_fz) * r->i_ss.channels);
 
-    r->to_work_format_buf.index = 0;
-    r->to_work_format_buf.length = r->w_sz * n_samples;
-    r->to_work_format_buf_size = fit_buf(r, &r->to_work_format_buf, r->to_work_format_buf_size);
+    if (have_leftover) {
+        leftover_length = r->to_work_format_buf.length;
+        out_n_samples += (unsigned) (leftover_length / r->w_sz);
+    }
+
+    fit_buf(r, &r->to_work_format_buf, r->w_sz * out_n_samples, &r->to_work_format_buf_size, leftover_length);
 
     src = pa_memblock_acquire_chunk(input);
-    dst = pa_memblock_acquire(r->to_work_format_buf.memblock);
+    dst = (uint8_t *) pa_memblock_acquire(r->to_work_format_buf.memblock) + leftover_length;
 
-    r->to_work_format_func(n_samples, src, dst);
+    if (r->to_work_format_func)
+        r->to_work_format_func(in_n_samples, src, dst);
+    else
+        memcpy(dst, src, input->length);
 
     pa_memblock_release(input->memblock);
     pa_memblock_release(r->to_work_format_buf.memblock);
@@ -1214,24 +1255,7 @@ static pa_memchunk *remap_channels(pa_resampler *r, pa_memchunk *input) {
     }
 
     out_n_samples = out_n_frames * r->o_ss.channels;
-    r->remap_buf.length = out_n_samples * r->w_sz;
-
-    if (have_leftover) {
-        if (r->remap_buf_size < r->remap_buf.length) {
-            pa_memblock *new_block = pa_memblock_new(r->mempool, r->remap_buf.length);
-
-            src = pa_memblock_acquire(r->remap_buf.memblock);
-            dst = pa_memblock_acquire(new_block);
-            memcpy(dst, src, leftover_length);
-            pa_memblock_release(r->remap_buf.memblock);
-            pa_memblock_release(new_block);
-
-            pa_memblock_unref(r->remap_buf.memblock);
-            r->remap_buf.memblock = new_block;
-            r->remap_buf_size = r->remap_buf.length;
-        }
-    } else
-        r->remap_buf_size = fit_buf(r, &r->remap_buf, r->remap_buf_size);
+    fit_buf(r, &r->remap_buf, out_n_samples * r->w_sz, &r->remap_buf_size, leftover_length);
 
     src = pa_memblock_acquire_chunk(input);
     dst = (uint8_t *) pa_memblock_acquire(r->remap_buf.memblock) + leftover_length;
@@ -1259,8 +1283,7 @@ static void save_leftover(pa_resampler *r, void *buf, size_t len) {
     pa_assert(len > 0);
 
     /* Store the leftover data. */
-    r->leftover_buf->length = len;
-    *r->leftover_buf_size = fit_buf(r, r->leftover_buf, *r->leftover_buf_size);
+    fit_buf(r, r->leftover_buf, len, r->leftover_buf_size, 0);
     *r->have_leftover = true;
 
     dst = pa_memblock_acquire(r->leftover_buf->memblock);
@@ -1282,10 +1305,7 @@ static pa_memchunk *resample(pa_resampler *r, pa_memchunk *input) {
     in_n_frames = (unsigned) (input->length / r->w_fz);
 
     out_n_frames = ((in_n_frames*r->o_ss.rate)/r->i_ss.rate)+EXTRA_FRAMES;
-
-    r->resample_buf.index = 0;
-    r->resample_buf.length = r->w_fz * out_n_frames;
-    r->resample_buf_size = fit_buf(r, &r->resample_buf, r->resample_buf_size);
+    fit_buf(r, &r->resample_buf, r->w_fz * out_n_frames, &r->resample_buf_size, 0);
 
     leftover_n_frames = r->impl.resample(r, input, in_n_frames, &r->resample_buf, &out_n_frames);
 
@@ -1315,10 +1335,7 @@ static pa_memchunk *convert_from_work_format(pa_resampler *r, pa_memchunk *input
 
     n_samples = (unsigned) (input->length / r->w_sz);
     n_frames = n_samples / r->o_ss.channels;
-
-    r->from_work_format_buf.index = 0;
-    r->from_work_format_buf.length = r->o_fz * n_frames;
-    r->from_work_format_buf_size = fit_buf(r, &r->from_work_format_buf, r->from_work_format_buf_size);
+    fit_buf(r, &r->from_work_format_buf, r->o_fz * n_frames, &r->from_work_format_buf_size, 0);
 
     src = pa_memblock_acquire_chunk(input);
     dst = pa_memblock_acquire(r->from_work_format_buf.memblock);
@@ -1341,8 +1358,16 @@ void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out)
 
     buf = (pa_memchunk*) in;
     buf = convert_to_work_format(r, buf);
-    buf = remap_channels(r, buf);
-    buf = resample(r, buf);
+
+    /* Try to save resampling effort: if we have more output channels than
+     * input channels, do resampling first, then remapping. */
+    if (r->o_ss.channels <= r->i_ss.channels) {
+        buf = remap_channels(r, buf);
+        buf = resample(r, buf);
+    } else {
+        buf = resample(r, buf);
+        buf = remap_channels(r, buf);
+    }
 
     if (buf->length) {
         buf = convert_from_work_format(r, buf);
@@ -1771,7 +1796,7 @@ static unsigned ffmpeg_resample(pa_resampler *r, const pa_memchunk *input, unsig
         int consumed_frames;
 
         /* Allocate a new block */
-        b = pa_memblock_new(r->mempool, ffmpeg_data->buf[c].length + in_n_frames * sizeof(int16_t));
+        b = pa_memblock_new(r->mempool, in_n_frames * sizeof(int16_t));
         p = pa_memblock_acquire(b);
 
         /* Now copy the input data, splitting up channels */
@@ -1820,7 +1845,6 @@ static unsigned ffmpeg_resample(pa_resampler *r, const pa_memchunk *input, unsig
 }
 
 static void ffmpeg_free(pa_resampler *r) {
-    unsigned c;
     struct ffmpeg_data *ffmpeg_data;
 
     pa_assert(r);
@@ -1828,14 +1852,9 @@ static void ffmpeg_free(pa_resampler *r) {
     ffmpeg_data = r->impl.data;
     if (ffmpeg_data->state)
         av_resample_close(ffmpeg_data->state);
-
-    for (c = 0; c < PA_ELEMENTSOF(ffmpeg_data->buf); c++)
-        if (ffmpeg_data->buf[c].memblock)
-            pa_memblock_unref(ffmpeg_data->buf[c].memblock);
 }
 
 static int ffmpeg_init(pa_resampler *r) {
-    unsigned c;
     struct ffmpeg_data *ffmpeg_data;
 
     pa_assert(r);
@@ -1854,9 +1873,6 @@ static int ffmpeg_init(pa_resampler *r) {
     r->impl.resample = ffmpeg_resample;
     r->impl.data = (void *) ffmpeg_data;
 
-    for (c = 0; c < PA_ELEMENTSOF(ffmpeg_data->buf); c++)
-        pa_memchunk_reset(&ffmpeg_data->buf[c]);
-
     return 0;
 }