/*
 * 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
 */
/*
 * Based on implementation kindly contributed by Switchlab, Ltd.
 */
#include <pjmedia/jbuf.h>
#include <pjmedia/errno.h>
#include <pj/pool.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/math.h>
#include <pj/string.h>


#define THIS_FILE   "jbuf.c"


/* Invalid sequence number, used as the initial value. */
#define INVALID_OFFSET          -9999

/* Maximum burst length, whenever an operation is bursting longer than
 * this value, JB will assume that the opposite operation was idle.
 */
#define MAX_BURST_MSEC          1000

/* Number of OP switches to be performed in JB_STATUS_INITIALIZING, before
 * JB can switch its states to JB_STATUS_PROCESSING.
 */
#define INIT_CYCLE              10


/* Maximum frame index in JB framelist (estimated).
 * As index is calculated as (RTP-timestamp/timestamp-span), the maximum index
 * usually ranging from MAXUINT32/9000 (10 fps video at 90kHz) to MAXUINT32/80
 * (10 ms audio at 8000Hz), lets take the 'lowest'.
 */
#define MAX_FRAME_INDEX         (0xFFFFFFFF/9000)


/* Minimal difference between JB size and 2*burst-level to perform
 * JB shrinking in static discard algorithm.
 */
#define STA_DISC_SAFE_SHRINKING_DIFF    1


/* Struct of JB internal buffer, represented in a circular buffer containing
 * frame content, frame type, frame length, and frame bit info.
 */
typedef struct jb_framelist_t
{
    /* Settings */
    unsigned         frame_size;        /**< maximum size of frame          */
    unsigned         max_count;         /**< maximum number of frames       */

    /* Buffers */
    char            *content;           /**< frame content array            */
    int             *frame_type;        /**< frame type array               */
    pj_size_t       *content_len;       /**< frame length array             */
    pj_uint32_t     *bit_info;          /**< frame bit info array           */
    pj_uint32_t     *ts;                /**< timestamp array                */

    /* States */
    unsigned         head;              /**< index of head, pointed frame
                                             will be returned by next GET   */
    unsigned         size;              /**< current size of framelist,
                                             including discarded frames.    */
    unsigned         discarded_num;     /**< current number of discarded
                                             frames.                        */
    int              origin;            /**< original index of flist_head   */

} jb_framelist_t;


typedef void (*discard_algo)(pjmedia_jbuf *jb);
static void jbuf_discard_static(pjmedia_jbuf *jb);
static void jbuf_discard_progressive(pjmedia_jbuf *jb);


struct pjmedia_jbuf
{
    /* Settings (consts) */
    pj_str_t        jb_name;            /**< jitter buffer name             */
    pj_size_t       jb_frame_size;      /**< frame size                     */
    unsigned        jb_frame_ptime;     /**< frame duration.                */
    unsigned        jb_frame_ptime_denum;/**< frame duration denumerator.   */
    pj_size_t       jb_max_count;       /**< capacity of jitter buffer,
                                             in frames                      */
    int             jb_init_prefetch;   /**< Initial prefetch               */
    int             jb_min_prefetch;    /**< Minimum allowable prefetch     */
    int             jb_max_prefetch;    /**< Maximum allowable prefetch     */
    int             jb_max_burst;       /**< maximum possible burst, whenever
                                             burst exceeds this value, it
                                             won't be included in level
                                             calculation                    */
    int             jb_min_shrink_gap;  /**< How often can we shrink        */
    discard_algo    jb_discard_algo;    /**< Discard algorithm              */
    unsigned        jb_min_delay;       /**< Minimum delay, in frames       */

    /* Buffer */
    jb_framelist_t  jb_framelist;       /**< the buffer                     */

    /* States */
    int             jb_level;           /**< delay between source &
                                             destination (calculated according
                                             of the number of burst get/put
                                             operations)                    */
    int             jb_max_hist_level;  /**< max level during the last level
                                             calculations                   */
    int             jb_stable_hist;     /**< num of times the delay has been
                                             lower then the prefetch num    */
    int             jb_last_op;         /**< last operation executed
                                             (put/get)                      */
    int             jb_eff_level;       /**< effective burst level          */
    int             jb_prefetch;        /**< no. of frame to insert before
                                             removing some (at the beginning
                                             of the framelist->content
                                             operation), the value may be
                                             continuously updated based on
                                             current frame burst level.     */
    pj_bool_t       jb_prefetching;     /**< flag if jbuf is prefetching.   */
    int             jb_status;          /**< status is 'init' until the first
                                             'put' operation                */
    int             jb_init_cycle_cnt;  /**< status is 'init' until the first
                                             'put' operation                */

    int             jb_discard_ref;     /**< Seq # of last frame deleted or
                                             discarded                      */
    unsigned        jb_discard_dist;    /**< Distance from jb_discard_ref
                                             to perform discard (in frm)    */

    /* Statistics */
    pj_math_stat    jb_delay;           /**< Delay statistics of jitter buffer
                                             (in ms)                        */
    pj_math_stat    jb_burst;           /**< Burst statistics (in frames)   */
    unsigned        jb_lost;            /**< Number of lost frames.         */
    unsigned        jb_discard;         /**< Number of discarded frames.    */
    unsigned        jb_empty;           /**< Number of empty/prefetching frame
                                             returned by GET. */
};


#define JB_STATUS_INITIALIZING  0
#define JB_STATUS_PROCESSING    1



/* Progressive discard algorithm introduced to reduce JB latency
 * by discarding incoming frames with adaptive aggressiveness based on
 * actual burst level.
 */
#define PROGRESSIVE_DISCARD 1

/* Internal JB frame flag, discarded frame will not be returned by JB to
 * application, it's just simply discarded.
 */
#define PJMEDIA_JB_DISCARDED_FRAME 1024



/* Enabling this would log the jitter buffer state about once per
 * second.
 */
#if 0
#  define TRACE__(args)     PJ_LOG(5,args)
#else
#  define TRACE__(args)
#endif

static pj_status_t jb_framelist_reset(jb_framelist_t *framelist);
static unsigned jb_framelist_remove_head(jb_framelist_t *framelist,
                                         unsigned count);

static pj_status_t jb_framelist_init( pj_pool_t *pool,
                                      jb_framelist_t *framelist,
                                      unsigned frame_size,
                                      unsigned max_count)
{
    PJ_ASSERT_RETURN(pool && framelist, PJ_EINVAL);

    pj_bzero(framelist, sizeof(jb_framelist_t));

    framelist->frame_size   = frame_size;
    framelist->max_count    = max_count;
    framelist->content      = (char*)
                              pj_pool_alloc(pool,
                                            (pj_size_t)framelist->frame_size*
                                            framelist->max_count);
    framelist->frame_type   = (int*)
                              pj_pool_alloc(pool,
                                            sizeof(framelist->frame_type[0])*
                                            framelist->max_count);
    framelist->content_len  = (pj_size_t*)
                              pj_pool_alloc(pool,
                                            sizeof(framelist->content_len[0])*
                                            framelist->max_count);
    framelist->bit_info     = (pj_uint32_t*)
                              pj_pool_alloc(pool,
                                            sizeof(framelist->bit_info[0])*
                                            framelist->max_count);
    framelist->ts           = (pj_uint32_t*)
                              pj_pool_alloc(pool,
                                            sizeof(framelist->ts[0])*
                                            framelist->max_count);

    return jb_framelist_reset(framelist);

}

static pj_status_t jb_framelist_destroy(jb_framelist_t *framelist)
{
    PJ_UNUSED_ARG(framelist);
    return PJ_SUCCESS;
}

static pj_status_t jb_framelist_reset(jb_framelist_t *framelist)
{
    framelist->head = 0;
    framelist->origin = INVALID_OFFSET;
    framelist->size = 0;
    framelist->discarded_num = 0;


    //pj_bzero(framelist->content,
    //       framelist->frame_size *
    //       framelist->max_count);

    pj_memset(framelist->frame_type,
              PJMEDIA_JB_MISSING_FRAME,
              sizeof(framelist->frame_type[0]) *
              framelist->max_count);

    pj_bzero(framelist->content_len,
             sizeof(framelist->content_len[0]) *
             framelist->max_count);

    //pj_bzero(framelist->bit_info,
    //       sizeof(framelist->bit_info[0]) *
    //       framelist->max_count);

    return PJ_SUCCESS;
}


static unsigned jb_framelist_size(const jb_framelist_t *framelist)
{
    return framelist->size;
}


static unsigned jb_framelist_eff_size(const jb_framelist_t *framelist)
{
    return (framelist->size - framelist->discarded_num);
}

static int jb_framelist_origin(const jb_framelist_t *framelist)
{
    return framelist->origin;
}


static pj_bool_t jb_framelist_get(jb_framelist_t *framelist,
                                  void *frame, pj_size_t *size,
                                  pjmedia_jb_frame_type *p_type,
                                  pj_uint32_t *bit_info,
                                  pj_uint32_t *ts,
                                  int *seq)
{
    if (framelist->size) {
        pj_bool_t prev_discarded = PJ_FALSE;

        /* Skip discarded frames */
        while (framelist->frame_type[framelist->head] ==
               PJMEDIA_JB_DISCARDED_FRAME)
        {
            jb_framelist_remove_head(framelist, 1);
            prev_discarded = PJ_TRUE;
        }

        /* Return the head frame if any */
        if (framelist->size) {
            if (prev_discarded) {
                /* Ticket #1188: when previous frame(s) was discarded, return
                 * 'missing' frame to trigger PLC to get smoother signal.
                 */
                *p_type = PJMEDIA_JB_MISSING_FRAME;
                if (size)
                    *size = 0;
                if (bit_info)
                    *bit_info = 0;
            } else {
                pj_size_t frm_size = framelist->content_len[framelist->head];
                pj_size_t max_size = size? *size : frm_size;
                pj_size_t copy_size = PJ_MIN(max_size, frm_size);

                /* Buffer size should not be smaller than frame size. */
                if (max_size < frm_size) {
                    pj_assert(!"Buffer too small");
                    PJ_LOG(4, (THIS_FILE, "Warning: buffer too small for the "
                                          "retrieved frame!"));
                }

                pj_memcpy(frame,
                          framelist->content +
                          framelist->head * framelist->frame_size,
                          copy_size);
                *p_type = (pjmedia_jb_frame_type)
                          framelist->frame_type[framelist->head];
                if (size)
                    *size = copy_size;
                if (bit_info)
                    *bit_info = framelist->bit_info[framelist->head];
            }
            if (ts)
                *ts = framelist->ts[framelist->head];
            if (seq)
                *seq = framelist->origin;

            //pj_bzero(framelist->content +
            //   framelist->head * framelist->frame_size,
            //   framelist->frame_size);
            framelist->frame_type[framelist->head] = PJMEDIA_JB_MISSING_FRAME;
            framelist->content_len[framelist->head] = 0;
            framelist->bit_info[framelist->head] = 0;
            framelist->ts[framelist->head] = 0;

            framelist->origin++;
            framelist->head = (framelist->head + 1) % framelist->max_count;
            framelist->size--;

            return PJ_TRUE;
        }
    }

    /* No frame available */
    pj_bzero(frame, framelist->frame_size);

    return PJ_FALSE;
}


static pj_bool_t jb_framelist_peek(jb_framelist_t *framelist,
                                   unsigned offset,
                                   const void **frame,
                                   pj_size_t *size,
                                   pjmedia_jb_frame_type *type,
                                   pj_uint32_t *bit_info,
                                   pj_uint32_t *ts,
                                   int *seq)
{
    unsigned pos, idx;

    if (offset >= jb_framelist_eff_size(framelist))
        return PJ_FALSE;

    pos = framelist->head;
    idx = offset;

    /* Find actual peek position, note there may be discarded frames */
    while (1) {
        if (framelist->frame_type[pos] != PJMEDIA_JB_DISCARDED_FRAME) {
            if (idx == 0)
                break;
            else
                --idx;
        }
        pos = (pos + 1) % framelist->max_count;
    }

    /* Return the frame pointer */
    if (frame)
        *frame = framelist->content + pos*framelist->frame_size;
    if (type)
        *type = (pjmedia_jb_frame_type)
                framelist->frame_type[pos];
    if (size)
        *size = framelist->content_len[pos];
    if (bit_info)
        *bit_info = framelist->bit_info[pos];
    if (ts)
        *ts = framelist->ts[pos];
    if (seq)
        *seq = framelist->origin + offset;

    return PJ_TRUE;
}


/* Remove oldest frames as many as param 'count' */
static unsigned jb_framelist_remove_head(jb_framelist_t *framelist,
                                         unsigned count)
{
    if (count > framelist->size)
        count = framelist->size;

    if (count) {
        /* may be done in two steps if overlapping */
        unsigned step1,step2;
        unsigned tmp = framelist->head+count;
        unsigned i;

        if (tmp > framelist->max_count) {
            step1 = framelist->max_count - framelist->head;
            step2 = count-step1;
        } else {
            step1 = count;
            step2 = 0;
        }

        for (i = framelist->head; i < (framelist->head + step1); ++i) {
            if (framelist->frame_type[i] == PJMEDIA_JB_DISCARDED_FRAME) {
                pj_assert(framelist->discarded_num > 0);
                framelist->discarded_num--;
            }
        }

        //pj_bzero(framelist->content +
        //          framelist->head * framelist->frame_size,
        //          step1*framelist->frame_size);
        pj_memset(framelist->frame_type+framelist->head,
                  PJMEDIA_JB_MISSING_FRAME,
                  step1*sizeof(framelist->frame_type[0]));
        pj_bzero(framelist->content_len+framelist->head,
                 step1*sizeof(framelist->content_len[0]));

        if (step2) {
            for (i = 0; i < step2; ++i) {
                if (framelist->frame_type[i] == PJMEDIA_JB_DISCARDED_FRAME) {
                    pj_assert(framelist->discarded_num > 0);
                    framelist->discarded_num--;
                }
            }
            //pj_bzero( framelist->content,
            //        step2*framelist->frame_size);
            pj_memset(framelist->frame_type,
                      PJMEDIA_JB_MISSING_FRAME,
                      step2*sizeof(framelist->frame_type[0]));
            pj_bzero (framelist->content_len,
                      step2*sizeof(framelist->content_len[0]));
        }

        /* update states */
        framelist->origin += count;
        framelist->head = (framelist->head + count) % framelist->max_count;
        framelist->size -= count;
    }

    return count;
}


static pj_status_t jb_framelist_put_at(jb_framelist_t *framelist,
                                       int index,
                                       const void *frame,
                                       unsigned frame_size,
                                       pj_uint32_t bit_info,
                                       pj_uint32_t ts,
                                       unsigned frame_type)
{
    int distance;
    unsigned pos;
    enum { MAX_MISORDER = 100 };
    enum { MAX_DROPOUT = 3000 };

    PJ_ASSERT_RETURN(frame_size <= framelist->frame_size, PJ_EINVAL);

    /* get distance of this frame to the first frame in the buffer */
    distance = index - framelist->origin;

    /* too late or sequence restart or far jump */
    if (distance < 0) {
        if (framelist->origin - index < MAX_MISORDER) {
            /* too late */
            TRACE__((THIS_FILE,"Put frame #%d: too late (distance=%d)",
                               index, distance));
            return PJ_ETOOSMALL;
        } else if (framelist->origin + framelist->size >= MAX_FRAME_INDEX) {
            /* sequence restart */
            TRACE__((THIS_FILE,"Put frame #%d: sequence restart (distance=%d, "
                               "orig=%d, size=%d)",
                               index, distance, framelist->origin,
                               framelist->size));
            framelist->origin = index - framelist->size;
            distance = framelist->size;
        } else {
            /* jump too far, reset the buffer */
            TRACE__((THIS_FILE,"Put frame #%d: far jump (distance=%d)",
                               index, distance));
            jb_framelist_reset(framelist);
            framelist->origin = index;
            distance = 0;
        }
    }

    /* if jbuf is empty, just reset the origin */
    if (framelist->size == 0) {
        pj_assert(framelist->discarded_num == 0);
        TRACE__((THIS_FILE,"Put frame #%d: origin reset (from %d) as JB empty",
                           index, framelist->origin));
        framelist->origin = index;
        distance = 0;
    }

    /* far jump, the distance is greater than buffer capacity */
    if (distance >= (int)framelist->max_count) {
        if (distance > MAX_DROPOUT) {
            /* jump too far, reset the buffer */
            TRACE__((THIS_FILE,"Put frame #%d: far jump (distance=%d)",
                               index, distance));
            jb_framelist_reset(framelist);
            framelist->origin = index;
            distance = 0;
        } else {
            /* otherwise, reject the frame */
            TRACE__((THIS_FILE,"Put frame #%d: rejected due to JB full",
                               index));
            return PJ_ETOOMANY;
        }
    }

    /* get the slot position */
    pos = (framelist->head + distance) % framelist->max_count;

    /* if the slot is occupied, it must be duplicated frame, ignore it. */
    if (framelist->frame_type[pos] != PJMEDIA_JB_MISSING_FRAME) {
        TRACE__((THIS_FILE,"Put frame #%d maybe a duplicate, ignored", index));
        return PJ_EEXISTS;
    }

    /* put the frame into the slot */
    framelist->frame_type[pos] = frame_type;
    framelist->content_len[pos] = frame_size;
    framelist->bit_info[pos] = bit_info;
    framelist->ts[pos] = ts;

    /* update framelist size */
    if (framelist->origin + (int)framelist->size <= index)
        framelist->size = distance + 1;

    if(PJMEDIA_JB_NORMAL_FRAME == frame_type) {
        /* copy frame content */
        pj_memcpy(framelist->content + pos * framelist->frame_size,
                  frame, frame_size);
    }

    return PJ_SUCCESS;
}


static pj_status_t jb_framelist_discard(jb_framelist_t *framelist,
                                        int index)
{
    unsigned pos;

    PJ_ASSERT_RETURN(index >= framelist->origin &&
                     index <  framelist->origin + (int)framelist->size,
                     PJ_EINVAL);

    /* Get the slot position */
    pos = (framelist->head + (index - framelist->origin)) %
          framelist->max_count;

    /* Discard the frame */
    framelist->frame_type[pos] = PJMEDIA_JB_DISCARDED_FRAME;
    framelist->discarded_num++;

    return PJ_SUCCESS;
}


enum pjmedia_jb_op
{
    JB_OP_INIT  = -1,
    JB_OP_PUT   = 1,
    JB_OP_GET   = 2
};


PJ_DEF(pj_status_t) pjmedia_jbuf_create(pj_pool_t *pool,
                                        const pj_str_t *name,
                                        unsigned frame_size,
                                        unsigned ptime,
                                        unsigned max_count,
                                        pjmedia_jbuf **p_jb)
{
    pjmedia_jbuf *jb;
    pj_status_t status;

    jb = PJ_POOL_ZALLOC_T(pool, pjmedia_jbuf);

    status = jb_framelist_init(pool, &jb->jb_framelist, frame_size, max_count);
    if (status != PJ_SUCCESS)
        return status;

    pj_strdup_with_null(pool, &jb->jb_name, name);
    jb->jb_frame_size    = frame_size;
    jb->jb_frame_ptime   = ptime;
    jb->jb_frame_ptime_denum = 1;
    jb->jb_prefetch      = PJ_MIN(PJMEDIA_JB_DEFAULT_INIT_DELAY,max_count*4/5);
    jb->jb_min_prefetch  = 0;
    jb->jb_max_prefetch  = max_count*4/5;
    jb->jb_max_count     = max_count;
    jb->jb_min_shrink_gap= PJMEDIA_JBUF_DISC_MIN_GAP / ptime;
    jb->jb_max_burst     = PJ_MAX(MAX_BURST_MSEC / ptime, max_count*3/4);

    pj_math_stat_init(&jb->jb_delay);
    pj_math_stat_init(&jb->jb_burst);

    pjmedia_jbuf_set_discard(jb, PJMEDIA_JB_DISCARD_PROGRESSIVE);
    pjmedia_jbuf_reset(jb);

    *p_jb = jb;
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_jbuf_set_ptime( pjmedia_jbuf *jb,
                                            unsigned ptime)
{
    return pjmedia_jbuf_set_ptime2(jb, ptime, 1);
}

PJ_DEF(pj_status_t) pjmedia_jbuf_set_ptime2(pjmedia_jbuf *jb,
                                            unsigned ptime,
                                            unsigned ptime_denum)
{
    PJ_ASSERT_RETURN(jb, PJ_EINVAL);

    jb->jb_frame_ptime    = ptime;
    jb->jb_frame_ptime_denum = ptime_denum;
    jb->jb_min_shrink_gap = PJMEDIA_JBUF_DISC_MIN_GAP * ptime_denum / ptime;
    jb->jb_max_burst      = (int)PJ_MAX(MAX_BURST_MSEC * ptime_denum / ptime,
                                        jb->jb_max_count*3/4);

    return PJ_SUCCESS;
}


/*
 * Set the jitter buffer to fixed delay mode. The default behavior
 * is to adapt the delay with actual packet delay.
 *
 */
PJ_DEF(pj_status_t) pjmedia_jbuf_set_fixed( pjmedia_jbuf *jb,
                                            unsigned prefetch)
{
    PJ_ASSERT_RETURN(jb, PJ_EINVAL);
    PJ_ASSERT_RETURN(prefetch <= jb->jb_max_count, PJ_EINVAL);

    jb->jb_min_prefetch = jb->jb_max_prefetch =
        jb->jb_prefetch = jb->jb_init_prefetch = prefetch;

    pjmedia_jbuf_set_discard(jb, PJMEDIA_JB_DISCARD_NONE);
    return PJ_SUCCESS;
}


/*
 * Set the jitter buffer to adaptive mode.
 */
PJ_DEF(pj_status_t) pjmedia_jbuf_set_adaptive( pjmedia_jbuf *jb,
                                               unsigned prefetch,
                                               unsigned min_prefetch,
                                               unsigned max_prefetch)
{
    PJ_ASSERT_RETURN(jb, PJ_EINVAL);
    PJ_ASSERT_RETURN(min_prefetch <= max_prefetch &&
                     prefetch <= max_prefetch &&
                     max_prefetch <= jb->jb_max_count,
                     PJ_EINVAL);

    jb->jb_prefetch = jb->jb_init_prefetch = prefetch;
    jb->jb_min_prefetch = min_prefetch;
    jb->jb_max_prefetch = max_prefetch;

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_jbuf_set_discard( pjmedia_jbuf *jb,
                                              pjmedia_jb_discard_algo algo)
{
    PJ_ASSERT_RETURN(jb, PJ_EINVAL);
    PJ_ASSERT_RETURN(algo >= PJMEDIA_JB_DISCARD_NONE &&
                     algo <= PJMEDIA_JB_DISCARD_PROGRESSIVE,
                     PJ_EINVAL);

    switch(algo) {
    case PJMEDIA_JB_DISCARD_PROGRESSIVE:
        jb->jb_discard_algo = &jbuf_discard_progressive;
        break;
    case PJMEDIA_JB_DISCARD_STATIC:
        jb->jb_discard_algo = &jbuf_discard_static;
        break;
    default:
        jb->jb_discard_algo = NULL;
        break;
    }

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_jbuf_reset(pjmedia_jbuf *jb)
{
    jb->jb_level         = 0;
    jb->jb_last_op       = JB_OP_INIT;
    jb->jb_stable_hist   = 0;
    jb->jb_status        = JB_STATUS_INITIALIZING;
    jb->jb_init_cycle_cnt= 0;
    jb->jb_max_hist_level= 0;
    jb->jb_prefetching   = (jb->jb_prefetch != 0);
    jb->jb_discard_dist  = 0;

    jb_framelist_reset(&jb->jb_framelist);

    PJ_LOG(5, (jb->jb_name.ptr, "Jitter buffer reset"));

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_jbuf_destroy(pjmedia_jbuf *jb)
{
    PJ_LOG(5, (jb->jb_name.ptr, ""
               "JB summary:\n"
               "  size=%d/eff=%d prefetch=%d level=%d\n"
               "  delay (min/max/avg/dev)=%d/%d/%d/%d ms\n"
               "  burst (min/max/avg/dev)=%d/%d/%d/%d frames\n"
               "  lost=%d discard=%d empty=%d",
               jb_framelist_size(&jb->jb_framelist),
               jb_framelist_eff_size(&jb->jb_framelist),
               jb->jb_prefetch, jb->jb_eff_level,
               jb->jb_delay.min, jb->jb_delay.max, jb->jb_delay.mean,
               pj_math_stat_get_stddev(&jb->jb_delay),
               jb->jb_burst.min, jb->jb_burst.max, jb->jb_burst.mean,
               pj_math_stat_get_stddev(&jb->jb_burst),
               jb->jb_lost, jb->jb_discard, jb->jb_empty));

    return jb_framelist_destroy(&jb->jb_framelist);
}

PJ_DEF(pj_bool_t) pjmedia_jbuf_is_full(const pjmedia_jbuf *jb)
{
    return jb->jb_framelist.size == jb->jb_framelist.max_count;
}

static void jbuf_calculate_jitter(pjmedia_jbuf *jb)
{
    int diff, cur_size;

    cur_size = jb_framelist_eff_size(&jb->jb_framelist);
    pj_math_stat_update(&jb->jb_burst, jb->jb_level);
    jb->jb_max_hist_level = PJ_MAX(jb->jb_max_hist_level, jb->jb_level);

    /* Burst level is decreasing */
    if (jb->jb_level < jb->jb_eff_level) {

        enum { STABLE_HISTORY_LIMIT = 20 };

        jb->jb_stable_hist++;

        /* Only update the effective level (and prefetch) if 'stable'
         * condition is reached (not just short time impulse)
         */
        if (jb->jb_stable_hist > STABLE_HISTORY_LIMIT) {

            diff = (jb->jb_eff_level - jb->jb_max_hist_level) / 3;

            if (diff < 1)
                diff = 1;

            /* Update effective burst level */
            jb->jb_eff_level -= diff;

            /* Update prefetch based on level */
            if (jb->jb_init_prefetch) {
                jb->jb_prefetch = jb->jb_eff_level;
                if (jb->jb_prefetch < jb->jb_min_prefetch)
                    jb->jb_prefetch = jb->jb_min_prefetch;
                if (jb->jb_prefetch > jb->jb_max_prefetch)
                    jb->jb_prefetch = jb->jb_max_prefetch;
            }

            /* Reset history */
            jb->jb_max_hist_level = 0;
            jb->jb_stable_hist = 0;

            TRACE__((jb->jb_name.ptr,"jb updated(1), lvl=%d pre=%d, size=%d",
                     jb->jb_eff_level, jb->jb_prefetch, cur_size));
            PJ_UNUSED_ARG(cur_size); /* Warning about unused var */
        }
    }

    /* Burst level is increasing */
    else if (jb->jb_level > jb->jb_eff_level) {

        /* Instaneous set effective burst level to recent maximum level */
        jb->jb_eff_level = PJ_MIN(jb->jb_max_hist_level,
                                  (int)(jb->jb_max_count*4/5));

        /* Update prefetch based on level */
        if (jb->jb_init_prefetch) {
            jb->jb_prefetch = jb->jb_eff_level;
            if (jb->jb_prefetch > jb->jb_max_prefetch)
                jb->jb_prefetch = jb->jb_max_prefetch;
            if (jb->jb_prefetch < jb->jb_min_prefetch)
                jb->jb_prefetch = jb->jb_min_prefetch;
        }

        jb->jb_stable_hist = 0;
        /* Do not reset max_hist_level. */
        //jb->jb_max_hist_level = 0;

        TRACE__((jb->jb_name.ptr,"jb updated(2), lvl=%d pre=%d, size=%d",
                 jb->jb_eff_level, jb->jb_prefetch, cur_size));
    }

    /* Level is unchanged */
    else {
        jb->jb_stable_hist = 0;
    }
}


static void jbuf_discard_static(pjmedia_jbuf *jb)
{
    /* These code is used for shortening the delay in the jitter buffer.
     * It needs shrink only when there is possibility of drift. Drift
     * detection is performed by inspecting the jitter buffer size, if
     * its size is twice of current burst level, there can be drift.
     *
     * Moreover, normally drift level is quite low, so JB shouldn't need
     * to shrink aggresively, it will shrink maximum one frame per
     * PJMEDIA_JBUF_DISC_MIN_GAP ms. Theoritically, JB may handle drift level
     * as much as = FRAME_PTIME/PJMEDIA_JBUF_DISC_MIN_GAP * 100%
     *
     * Whenever there is drift, where PUT > GET, this method will keep
     * the latency (JB size) as much as twice of burst level.
     */

    /* Shrinking due of drift will be implicitly done by progressive discard,
     * so just disable it when progressive discard is active.
     */
    int diff, burst_level;
    unsigned cur_size;

    burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level);
    cur_size = jb_framelist_eff_size(&jb->jb_framelist);

    /* Don't discard if delay is lower than or equal to setting */
    if (cur_size <= jb->jb_min_delay)
        return;

    diff = cur_size - burst_level*2;

    if (diff >= STA_DISC_SAFE_SHRINKING_DIFF) {
        int seq_origin;

        /* Check and adjust jb_discard_ref, in case there was
         * seq restart
         */
        seq_origin = jb_framelist_origin(&jb->jb_framelist);
        if (seq_origin < jb->jb_discard_ref)
            jb->jb_discard_ref = seq_origin;

        if (seq_origin - jb->jb_discard_ref >= jb->jb_min_shrink_gap)
        {
            /* Shrink slowly, one frame per cycle */
            diff = 1;

            /* Drop frame(s)! */
            diff = jb_framelist_remove_head(&jb->jb_framelist, diff);
            jb->jb_discard_ref = jb_framelist_origin(&jb->jb_framelist);
            jb->jb_discard += diff;

            TRACE__((jb->jb_name.ptr,
                     "JB shrinking %d frame(s), cur size=%d", diff,
                     jb_framelist_eff_size(&jb->jb_framelist)));
        }
    }
}


static void jbuf_discard_progressive(pjmedia_jbuf *jb)
{
    unsigned cur_size, burst_level, overflow, T, discard_dist;
    int last_seq;

    /* Should be done in PUT operation */
    if (jb->jb_last_op != JB_OP_PUT)
        return;

    /* Check if latency is longer than burst or minimum delay */
    cur_size = jb_framelist_eff_size(&jb->jb_framelist);
    burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level);
    if (cur_size <= burst_level || cur_size <= jb->jb_min_delay) {
        /* Reset any scheduled discard */
        jb->jb_discard_dist = 0;
        return;
    }

    /* Estimate discard duration needed for adjusting latency */
    if (burst_level <= PJMEDIA_JBUF_PRO_DISC_MIN_BURST)
        T = PJMEDIA_JBUF_PRO_DISC_T1;
    else if (burst_level >= PJMEDIA_JBUF_PRO_DISC_MAX_BURST)
        T = PJMEDIA_JBUF_PRO_DISC_T2;
    else
        T = PJMEDIA_JBUF_PRO_DISC_T1 +
            (PJMEDIA_JBUF_PRO_DISC_T2 - PJMEDIA_JBUF_PRO_DISC_T1) *
            (burst_level - PJMEDIA_JBUF_PRO_DISC_MIN_BURST) /
            (PJMEDIA_JBUF_PRO_DISC_MAX_BURST-PJMEDIA_JBUF_PRO_DISC_MIN_BURST);

    /* Calculate current discard distance */
    overflow = cur_size - PJ_MAX(burst_level, jb->jb_min_delay);
    discard_dist = T * jb->jb_frame_ptime_denum / overflow /
                   jb->jb_frame_ptime;

    /* Get last seq number in the JB */
    last_seq = jb_framelist_origin(&jb->jb_framelist) +
               jb_framelist_size(&jb->jb_framelist) - 1;

    /* Setup new discard schedule if none, otherwise, update the existing
     * discard schedule (can be delayed or accelerated).
     */
    if (jb->jb_discard_dist == 0) {
        /* Setup new discard schedule */
        jb->jb_discard_ref = last_seq;
    } else if (last_seq < jb->jb_discard_ref) {
        /* Seq restarted, update discard reference */
        jb->jb_discard_ref = last_seq;
    }
    jb->jb_discard_dist = PJ_MAX(jb->jb_min_shrink_gap, (int)discard_dist);

    /* Check if we need to discard now */
    if (last_seq >= (jb->jb_discard_ref + (int)jb->jb_discard_dist)) {
        int discard_seq;

        discard_seq = jb->jb_discard_ref + jb->jb_discard_dist;
        if (discard_seq < jb_framelist_origin(&jb->jb_framelist))
            discard_seq = jb_framelist_origin(&jb->jb_framelist);

        jb_framelist_discard(&jb->jb_framelist, discard_seq);

        TRACE__((jb->jb_name.ptr,
                "Discard #%d: ref=#%d dist=%d orig=%d size=%d/%d "
                "burst=%d/%d",
                discard_seq,
                jb->jb_discard_ref,
                jb->jb_discard_dist,
                jb_framelist_origin(&jb->jb_framelist),
                cur_size,
                jb_framelist_size(&jb->jb_framelist),
                jb->jb_eff_level,
                burst_level));

        /* Update discard reference */
        jb->jb_discard_ref = discard_seq;
    }
}


PJ_INLINE(void) jbuf_update(pjmedia_jbuf *jb, int oper)
{
    if(jb->jb_last_op != oper) {
        jb->jb_last_op = oper;

        if (jb->jb_status == JB_STATUS_INITIALIZING) {
            /* Switch status 'initializing' -> 'processing' after some OP
             * switch cycles and current OP is GET (burst level is calculated
             * based on PUT burst), so burst calculation is guaranted to be
             * performed right after the status switching.
             */
            if (++jb->jb_init_cycle_cnt >= INIT_CYCLE && oper == JB_OP_GET) {
                jb->jb_status = JB_STATUS_PROCESSING;
                /* To make sure the burst calculation will be done right after
                 * this, adjust burst level if it exceeds max burst level.
                 */
                jb->jb_level = PJ_MIN(jb->jb_level, jb->jb_max_burst);
            } else {
                jb->jb_level = 0;
                return;
            }
        }

        /* Perform jitter calculation based on PUT burst-level only, since
         * GET burst-level may not be accurate, e.g: when VAD is active.
         * Note that when burst-level is too big, i.e: exceeds jb_max_burst,
         * the GET op may be idle, in this case, we better skip the jitter
         * calculation.
         */
        if (oper == JB_OP_GET && jb->jb_level <= jb->jb_max_burst)
            jbuf_calculate_jitter(jb);

        jb->jb_level = 0;
    }

    /* Call discard algorithm */
    if (jb->jb_status == JB_STATUS_PROCESSING && jb->jb_discard_algo) {
        (*jb->jb_discard_algo)(jb);
    }
}

PJ_DEF(void) pjmedia_jbuf_put_frame( pjmedia_jbuf *jb,
                                     const void *frame,
                                     pj_size_t frame_size,
                                     int frame_seq)
{
    pjmedia_jbuf_put_frame3(jb, frame, frame_size, 0, frame_seq, 0, NULL);
}

PJ_DEF(void) pjmedia_jbuf_put_frame2(pjmedia_jbuf *jb,
                                     const void *frame,
                                     pj_size_t frame_size,
                                     pj_uint32_t bit_info,
                                     int frame_seq,
                                     pj_bool_t *discarded)
{
    pjmedia_jbuf_put_frame3(jb, frame, frame_size, bit_info, frame_seq, 0,
                            discarded);
}

PJ_DEF(void) pjmedia_jbuf_put_frame3(pjmedia_jbuf *jb,
                                     const void *frame,
                                     pj_size_t frame_size,
                                     pj_uint32_t bit_info,
                                     int frame_seq,
                                     pj_uint32_t ts,
                                     pj_bool_t *discarded)
{
    pj_size_t min_frame_size;
    int new_size, cur_size;
    pj_status_t status;

    cur_size = jb_framelist_eff_size(&jb->jb_framelist);

    /* Check if frame size is larger than JB frame size */
    if (frame_size > jb->jb_frame_size) {
        PJ_LOG(4, (THIS_FILE, "Warning: frame too large for jitter buffer, "
                   "it will be truncated!"));
    }

    /* Attempt to store the frame */
    min_frame_size = PJ_MIN(frame_size, jb->jb_frame_size);
    status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame,
                                 (unsigned)min_frame_size, bit_info, ts,
                                 PJMEDIA_JB_NORMAL_FRAME);

    /* Jitter buffer is full, remove some older frames */
    while (status == PJ_ETOOMANY) {
        int distance;
        unsigned removed;

        /* Remove as few as possible just to make this frame in. Note that
         * the cases of seq-jump, out-of-order, and seq restart should have
         * been handled/normalized by previous call of jb_framelist_put_at().
         * So we're confident about 'distance' value here.
         */
        distance = (frame_seq - jb_framelist_origin(&jb->jb_framelist)) -
                   (int)jb->jb_max_count + 1;
        pj_assert(distance > 0);

        removed = jb_framelist_remove_head(&jb->jb_framelist, distance);
        status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame,
                                     (unsigned)min_frame_size, bit_info, ts,
                                     PJMEDIA_JB_NORMAL_FRAME);

        jb->jb_discard += removed;
    }

    /* Get new JB size after PUT */
    new_size = jb_framelist_eff_size(&jb->jb_framelist);

    /* Return the flag if this frame is discarded */
    if (discarded)
        *discarded = (status != PJ_SUCCESS);

    if (status == PJ_SUCCESS) {
        if (jb->jb_prefetching) {
            TRACE__((jb->jb_name.ptr, "PUT prefetch_cnt=%d/%d",
                     new_size, jb->jb_prefetch));
            if (new_size >= jb->jb_prefetch)
                jb->jb_prefetching = PJ_FALSE;
        }
        jb->jb_level += (new_size > cur_size ? new_size-cur_size : 1);
        jbuf_update(jb, JB_OP_PUT);
    } else
        jb->jb_discard++;
}

/*
 * Get frame from jitter buffer.
 */
PJ_DEF(void) pjmedia_jbuf_get_frame( pjmedia_jbuf *jb,
                                     void *frame,
                                     char *p_frame_type)
{
    pjmedia_jbuf_get_frame3(jb, frame, NULL, p_frame_type, NULL,
                            NULL, NULL);
}

/*
 * Get frame from jitter buffer.
 */
PJ_DEF(void) pjmedia_jbuf_get_frame2(pjmedia_jbuf *jb,
                                     void *frame,
                                     pj_size_t *size,
                                     char *p_frame_type,
                                     pj_uint32_t *bit_info)
{
    pjmedia_jbuf_get_frame3(jb, frame, size, p_frame_type, bit_info,
                            NULL, NULL);
}

/*
 * Get frame from jitter buffer.
 */
PJ_DEF(void) pjmedia_jbuf_get_frame3(pjmedia_jbuf *jb,
                                     void *frame,
                                     pj_size_t *size,
                                     char *p_frame_type,
                                     pj_uint32_t *bit_info,
                                     pj_uint32_t *ts,
                                     int *seq)
{
    if (jb->jb_prefetching) {

        /* Can't return frame because jitter buffer is filling up
         * minimum prefetch.
         */

        //pj_bzero(frame, jb->jb_frame_size);
        *p_frame_type = PJMEDIA_JB_ZERO_PREFETCH_FRAME;
        if (size)
            *size = 0;

        TRACE__((jb->jb_name.ptr, "GET prefetch_cnt=%d/%d",
                 jb_framelist_eff_size(&jb->jb_framelist), jb->jb_prefetch));

        jb->jb_empty++;

    } else if (jb_framelist_eff_size(&jb->jb_framelist) < jb->jb_min_delay) {
        *p_frame_type = PJMEDIA_JB_MISSING_FRAME;
        if (size)
            *size = 0;
    } else {

        pjmedia_jb_frame_type ftype = PJMEDIA_JB_NORMAL_FRAME;
        pj_bool_t res;

        /* Try to retrieve a frame from frame list */
        res = jb_framelist_get(&jb->jb_framelist, frame, size, &ftype,
                               bit_info, ts, seq);
        if (res) {
            /* We've successfully retrieved a frame from the frame list, but
             * the frame could be a blank frame!
             */
            if (ftype == PJMEDIA_JB_NORMAL_FRAME) {
                *p_frame_type = PJMEDIA_JB_NORMAL_FRAME;
            } else {
                *p_frame_type = PJMEDIA_JB_MISSING_FRAME;
                jb->jb_lost++;
            }

            /* Store delay history at the first GET */
            if (jb->jb_last_op == JB_OP_PUT) {
                unsigned cur_size;

                /* We've just retrieved one frame, so add one to cur_size */
                cur_size = jb_framelist_eff_size(&jb->jb_framelist) + 1;
                pj_math_stat_update(&jb->jb_delay,
                                    cur_size * jb->jb_frame_ptime /
                                    jb->jb_frame_ptime_denum);
            }
        } else {
            /* Jitter buffer is empty */
            if (jb->jb_prefetch)
                jb->jb_prefetching = PJ_TRUE;

            //pj_bzero(frame, jb->jb_frame_size);
            *p_frame_type = PJMEDIA_JB_ZERO_EMPTY_FRAME;
            if (size)
                *size = 0;

            jb->jb_empty++;
        }
    }

    jb->jb_level++;
    jbuf_update(jb, JB_OP_GET);
}

/*
 * Get jitter buffer state.
 */
PJ_DEF(pj_status_t) pjmedia_jbuf_get_state( const pjmedia_jbuf *jb,
                                            pjmedia_jb_state *state )
{
    PJ_ASSERT_RETURN(jb && state, PJ_EINVAL);

    state->frame_size = (unsigned)jb->jb_frame_size;
    state->min_prefetch = jb->jb_min_prefetch;
    state->max_prefetch = jb->jb_max_prefetch;
    state->max_count = (unsigned)jb->jb_max_count;
    state->min_delay_set = jb->jb_min_delay;

    state->burst = jb->jb_eff_level;
    state->prefetch = jb->jb_prefetch;
    state->size = jb_framelist_eff_size(&jb->jb_framelist);

    state->avg_delay = jb->jb_delay.mean;
    state->min_delay = jb->jb_delay.min;
    state->max_delay = jb->jb_delay.max;
    state->dev_delay = pj_math_stat_get_stddev(&jb->jb_delay);

    state->avg_burst = jb->jb_burst.mean;
    state->empty = jb->jb_empty;
    state->discard = jb->jb_discard;
    state->lost = jb->jb_lost;

    return PJ_SUCCESS;
}


PJ_DEF(void) pjmedia_jbuf_peek_frame( pjmedia_jbuf *jb,
                                      unsigned offset,
                                      const void **frame,
                                      pj_size_t *size,
                                      char *p_frm_type,
                                      pj_uint32_t *bit_info,
                                      pj_uint32_t *ts,
                                      int *seq)
{
    pjmedia_jb_frame_type ftype;
    pj_bool_t res;

    res = jb_framelist_peek(&jb->jb_framelist, offset, frame, size, &ftype,
                            bit_info, ts, seq);
    if (!res)
        *p_frm_type = PJMEDIA_JB_ZERO_EMPTY_FRAME;
    else if (ftype == PJMEDIA_JB_NORMAL_FRAME)
        *p_frm_type = PJMEDIA_JB_NORMAL_FRAME;
    else
        *p_frm_type = PJMEDIA_JB_MISSING_FRAME;
}


PJ_DEF(unsigned) pjmedia_jbuf_remove_frame(pjmedia_jbuf *jb,
                                           unsigned frame_cnt)
{
    unsigned count, last_discard_num;

    last_discard_num = jb->jb_framelist.discarded_num;
    count = jb_framelist_remove_head(&jb->jb_framelist, frame_cnt);

    /* Remove some more when there were discarded frames included */
    while (jb->jb_framelist.discarded_num < last_discard_num) {
        /* Calculate frames count to be removed next */
        frame_cnt = last_discard_num - jb->jb_framelist.discarded_num;

        /* Normalize non-discarded frames count just been removed */
        count -= frame_cnt;

        /* Remove more frames */
        last_discard_num = jb->jb_framelist.discarded_num;
        count += jb_framelist_remove_head(&jb->jb_framelist, frame_cnt);
    }

    return count;
}


PJ_DEF(pj_status_t) pjmedia_jbuf_set_min_delay(pjmedia_jbuf *jb,
                                               unsigned min_delay)
{
    PJ_ASSERT_RETURN(jb, PJ_EINVAL);

    /* Convert milliseconds to frames */
    min_delay *= jb->jb_frame_ptime_denum;
    jb->jb_min_delay = min_delay / jb->jb_frame_ptime;
    if (min_delay % jb->jb_frame_ptime)
        jb->jb_min_delay++;

    /* Should not be higher than half of jitter buffer capacity */
    if (jb->jb_min_delay > jb->jb_max_count/2)
        jb->jb_min_delay = (unsigned)jb->jb_max_count/2;

    return PJ_SUCCESS;
}
