/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */
#include <pjmedia/resample.h>
#include <pjmedia/errno.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/pool.h>

/*
 * HOW TO ACTIVATE LIBSAMPLERATE (a.k.a SRC/Secret Rabbit Code) AS
 * PJMEDIA'S SAMPLE RATE CONVERSION BACKEND
 *
 * See README.txt in third_party/samplerate directory.
 */


#if PJMEDIA_RESAMPLE_IMP==PJMEDIA_RESAMPLE_LIBSAMPLERATE

#include <samplerate.h>

#define THIS_FILE   "resample_libsamplerate.c"

#if defined(_MSC_VER)
#   ifdef _DEBUG
#       pragma comment( lib, "../../third_party/lib/libsamplerate-i386-win32-vc-debug.lib")
#   else
#       pragma comment( lib, "../../third_party/lib/libsamplerate-i386-win32-vc-release.lib")
#   endif
#endif


struct pjmedia_resample
{
    SRC_STATE   *state;
    unsigned     in_samples;
    unsigned     out_samples;
    float       *frame_in, *frame_out;
    unsigned     in_extra, out_extra;
    double       ratio;
};


PJ_DEF(pj_status_t) pjmedia_resample_create( pj_pool_t *pool,
                                             pj_bool_t high_quality,
                                             pj_bool_t large_filter,
                                             unsigned channel_count,
                                             unsigned rate_in,
                                             unsigned rate_out,
                                             unsigned samples_per_frame,
                                             pjmedia_resample **p_resample)
{
    pjmedia_resample *resample;
    int type, err, i=0;
    enum { MAX_KICKOFF = 10 };

    PJ_ASSERT_RETURN(pool && p_resample && rate_in &&
                     rate_out && samples_per_frame, PJ_EINVAL);

    resample = PJ_POOL_ZALLOC_T(pool, pjmedia_resample);
    PJ_ASSERT_RETURN(resample, PJ_ENOMEM);

    /* Select conversion type */
    if (high_quality) {
        type = large_filter ? SRC_SINC_BEST_QUALITY : SRC_SINC_MEDIUM_QUALITY;
    } else {
        type = large_filter ? SRC_SINC_FASTEST : SRC_LINEAR;
    }

    /* Create converter */
    resample->state = src_new(type, channel_count, &err);
    if (resample->state == NULL) {
        PJ_LOG(4,(THIS_FILE, "Error creating resample: %s", 
                  src_strerror(err)));
        return PJMEDIA_ERROR;
    }

    /* Calculate ratio */
    resample->ratio = rate_out * 1.0 / rate_in;

    /* Calculate number of samples for input and output */
    resample->in_samples = samples_per_frame;
    resample->out_samples = rate_out / (rate_in / samples_per_frame);

    resample->frame_in = (float*) 
                         pj_pool_calloc(pool, 
                                        resample->in_samples + 8, 
                                        sizeof(float));

    resample->frame_out = (float*) 
                          pj_pool_calloc(pool, 
                                         resample->out_samples + 8, 
                                         sizeof(float));

    /* Set the converter ratio */
    err = src_set_ratio(resample->state, resample->ratio);
    if (err != 0) {
        PJ_LOG(4,(THIS_FILE, "Error creating resample: %s", 
                  src_strerror(err)));
        return PJMEDIA_ERROR;
    }

    /* Kick off. Some initial processes may return samples less than
     * samples_per_frame for unknown reason (lookahead buffering?), which
     * will cause false 'extra' mechanism and introduce click noise in
     * the whole session. So now, let's feed the engine with silent
     * audio frames, until full frame is returned, to avoid that.
     */
    while (++i <= MAX_KICKOFF) {
        SRC_DATA src_data;
        pj_bzero(&src_data, sizeof(src_data));
        src_data.data_in = resample->frame_in;
        src_data.data_out = resample->frame_out;
        src_data.input_frames = resample->in_samples;
        src_data.output_frames = resample->out_samples;
        src_data.src_ratio = resample->ratio;
        src_process(resample->state, &src_data);
        if (src_data.output_frames_gen == (int)resample->out_samples)
            break;

        PJ_LOG(5,(THIS_FILE,
               "Resample kickoff #%d: output_frames_gen=%d (expected=%d)%s",
               i, src_data.output_frames_gen, resample->out_samples,
               (i==MAX_KICKOFF?", warning: kickoff failed!":"")));
    }

    /* Done */

    PJ_LOG(5,(THIS_FILE, 
              "Resample using libsamplerate %s, type=%s (%s), "
              "ch=%d, in/out rate=%d/%d", 
              src_get_version(),
              src_get_name(type), src_get_description(type),
              channel_count, rate_in, rate_out));

    *p_resample = resample;

    return PJ_SUCCESS;
}


PJ_DEF(void) pjmedia_resample_run( pjmedia_resample *resample,
                                   const pj_int16_t *input,
                                   pj_int16_t *output )
{
    SRC_DATA src_data;

    /* Convert samples to float */
    src_short_to_float_array(input, resample->frame_in, 
                             resample->in_samples);

    if (resample->in_extra) {
        unsigned i;

        for (i=0; i<resample->in_extra; ++i)
            resample->frame_in[resample->in_samples+i] =
                resample->frame_in[resample->in_samples-1];
    }

    /* Prepare SRC_DATA */
    pj_bzero(&src_data, sizeof(src_data));
    src_data.data_in = resample->frame_in;
    src_data.data_out = resample->frame_out;
    src_data.input_frames = resample->in_samples + resample->in_extra;
    src_data.output_frames = resample->out_samples + resample->out_extra;
    src_data.src_ratio = resample->ratio;

    /* Process! */
    src_process(resample->state, &src_data);

    /* Convert output back to short */
    src_float_to_short_array(resample->frame_out, output,
                             src_data.output_frames_gen);

    /* Replay last sample if conversion couldn't fill up the whole 
     * frame. This could happen for example with 22050 to 16000 conversion.
     */
    if (src_data.output_frames_gen < (int)resample->out_samples) {
        unsigned i;

        if (resample->in_extra < 4)
            resample->in_extra++;

        for (i=src_data.output_frames_gen; 
             i<resample->out_samples; ++i)
        {
            output[i] = output[src_data.output_frames_gen-1];
        }
    }
}


PJ_DEF(unsigned) pjmedia_resample_get_input_size(pjmedia_resample *resample)
{
    PJ_ASSERT_RETURN(resample != NULL, 0);
    return resample->in_samples;
}


PJ_DEF(void) pjmedia_resample_destroy(pjmedia_resample *resample)
{
    PJ_ASSERT_ON_FAIL(resample, return);
    if (resample->state) {
        src_delete(resample->state);
        resample->state = NULL;

        PJ_LOG(5,(THIS_FILE, "Resample destroyed"));
    }
}

#else /* PJMEDIA_RESAMPLE_IMP==PJMEDIA_RESAMPLE_LIBSAMPLERATE */

int pjmedia_libsamplerate_excluded;

#endif  /* PJMEDIA_RESAMPLE_IMP==PJMEDIA_RESAMPLE_LIBSAMPLERATE */

