diff options
Diffstat (limited to 'pjmedia/src/pjmedia/jbuf.c')
-rw-r--r-- | pjmedia/src/pjmedia/jbuf.c | 369 |
1 files changed, 218 insertions, 151 deletions
diff --git a/pjmedia/src/pjmedia/jbuf.c b/pjmedia/src/pjmedia/jbuf.c index 52a3b0eb..94a88c15 100644 --- a/pjmedia/src/pjmedia/jbuf.c +++ b/pjmedia/src/pjmedia/jbuf.c @@ -32,14 +32,6 @@ #define THIS_FILE "jbuf.c" -/* Minimal difference between JB size and 2*burst-level to perform - * JB shrinking. - */ -#define SAFE_SHRINKING_DIFF 1 - -/* Minimal gap (in ms) between JB shrinking */ -#define MIN_SHRINK_GAP_MSEC 200 - /* Invalid sequence number, used as the initial value. */ #define INVALID_OFFSET -9999 @@ -54,6 +46,12 @@ #define INIT_CYCLE 10 +/* 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. */ @@ -82,6 +80,11 @@ typedef struct jb_framelist_t } 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) */ @@ -98,6 +101,7 @@ struct pjmedia_jbuf won't be included in level calculation */ int jb_min_shrink_gap; /**< How often can we shrink */ + discard_algo jb_discard_algo; /**< Discard algorithm */ /* Buffer */ jb_framelist_t jb_framelist; /**< the buffer */ @@ -120,13 +124,16 @@ struct pjmedia_jbuf 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_last_del_seq; /**< Seq # of last frame deleted */ - int jb_last_discard_seq;/**< Seq # of last frame discarded */ + 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 @@ -141,7 +148,6 @@ struct pjmedia_jbuf #define JB_STATUS_INITIALIZING 0 #define JB_STATUS_PROCESSING 1 -#define JB_STATUS_PREFETCHING 2 @@ -451,7 +457,7 @@ static pj_status_t jb_framelist_put_at(jb_framelist_t *framelist, enum { MAX_MISORDER = 100 }; enum { MAX_DROPOUT = 3000 }; - assert(frame_size <= framelist->frame_size); + PJ_ASSERT_RETURN(frame_size <= framelist->frame_size, PJ_EINVAL); /* too late or sequence restart */ if (index < framelist->origin) { @@ -507,15 +513,32 @@ static pj_status_t jb_framelist_put_at(jb_framelist_t *framelist, /* copy frame content */ pj_memcpy(framelist->content + pos * framelist->frame_size, frame, frame_size); - return PJ_SUCCESS; - } else { - /* frame is being discarded */ - framelist->discarded_num++; - return PJ_EIGNORED; } + + 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 { @@ -548,13 +571,13 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_create(pj_pool_t *pool, jb->jb_min_prefetch = 0; jb->jb_max_prefetch = max_count*4/5; jb->jb_max_count = max_count; - jb->jb_min_shrink_gap= MIN_SHRINK_GAP_MSEC / ptime; + 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); - jb->jb_last_discard_seq = 0; 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; @@ -589,7 +612,7 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_set_adaptive( pjmedia_jbuf *jb, unsigned max_prefetch) { PJ_ASSERT_RETURN(jb, PJ_EINVAL); - PJ_ASSERT_RETURN(min_prefetch < max_prefetch && + PJ_ASSERT_RETURN(min_prefetch <= max_prefetch && prefetch <= max_prefetch && max_prefetch <= jb->jb_max_count, PJ_EINVAL); @@ -602,6 +625,30 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_set_adaptive( pjmedia_jbuf *jb, } +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; @@ -610,6 +657,8 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_reset(pjmedia_jbuf *jb) 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); @@ -621,11 +670,13 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_destroy(pjmedia_jbuf *jb) { PJ_LOG(5, (jb->jb_name.ptr, "" "JB summary:\n" - " size=%d prefetch=%d level=%d\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->jb_framelist.size, jb->jb_prefetch, jb->jb_eff_level, + 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, @@ -712,6 +763,136 @@ static void jbuf_calculate_jitter(pjmedia_jbuf *jb) } } + +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; + + burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level); + diff = jb_framelist_eff_size(&jb->jb_framelist) - 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 */ + 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) { + /* 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 - burst_level; + discard_dist = T / 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) { @@ -747,63 +928,10 @@ PJ_INLINE(void) jbuf_update(pjmedia_jbuf *jb, int oper) jb->jb_level = 0; } - /* 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 - * MIN_SHRINK_GAP_MSEC ms. Theoritically, JB may handle drift level - * as much as = FRAME_PTIME/MIN_SHRINK_GAP_MSEC * 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. - */ -#if !PROGRESSIVE_DISCARD - - if (jb->jb_status != JB_STATUS_PROCESSING) - return; - - { - int diff, burst_level; - - burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level); - diff = jb_framelist_eff_size(&jb->jb_framelist) - burst_level*2; - - if (diff >= SAFE_SHRINKING_DIFF) { - int seq_origin; - - /* Check and adjust jb_last_del_seq, in case there was - * seq restart - */ - seq_origin = jb_framelist_origin(&jb->jb_framelist); - if (seq_origin < jb->jb_last_del_seq) - jb->jb_last_del_seq = seq_origin; - - if (seq_origin - jb->jb_last_del_seq >= 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_last_del_seq = 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))); - } - } + /* Call discard algorithm */ + if (jb->jb_status == JB_STATUS_PROCESSING && jb->jb_discard_algo) { + (*jb->jb_discard_algo)(jb); } - -#endif /* !PROGRESSIVE_DISCARD */ - } PJ_DEF(void) pjmedia_jbuf_put_frame( pjmedia_jbuf *jb, @@ -834,96 +962,35 @@ PJ_DEF(void) pjmedia_jbuf_put_frame3(pjmedia_jbuf *jb, pj_bool_t *discarded) { pj_size_t min_frame_size; - int new_size, cur_size, frame_type = PJMEDIA_JB_NORMAL_FRAME; + int new_size, cur_size; pj_status_t status; cur_size = jb_framelist_eff_size(&jb->jb_framelist); -#if PROGRESSIVE_DISCARD - { - unsigned interval, seq_delta; - unsigned burst_level, burst_factor; - - /* Calculating discard interval (aggressiveness) based on - * (current size / burst level). - */ - if (jb->jb_status == JB_STATUS_PROCESSING) { - burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level); - burst_factor = cur_size / burst_level; - /* Tolerate small spikes */ - if ((burst_level <= 5) && (burst_factor < 3)) - burst_factor = 0; - } else { - burst_factor = 0; - } - - switch (burst_factor) { - case 0: - interval = 0; - break; - case 1: - interval = 7; - break; - case 2: - interval = 5; - break; - default: - interval = 4; - break; - } - - /* Do the math now to see if we should discard this packet. - * Calculate the distance from the last sequence - * discarded. If negative, then this is an out of - * order frame so just proceed with discard. Else - * see if the delta is at least the intervals worth away - * from the last frame discarded. - */ - seq_delta = (pj_uint16_t)(frame_seq - jb->jb_last_discard_seq); - if ((0 != interval) && (seq_delta >= interval)) { - frame_type = PJMEDIA_JB_DISCARDED_FRAME; - jb->jb_last_discard_seq = frame_seq; - - TRACE__((jb->jb_name.ptr, - "Discarding frame #%d: eff=%d disc=%d orig:%d" - " seq_delta:%d", - frame_seq, - cur_size, - jb_framelist_size(&jb->jb_framelist) - cur_size, - jb_framelist_origin(&jb->jb_framelist), - (int)seq_delta)); - } - } -#endif /* PROGRESSIVE_DISCARD */ - - /* 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, - min_frame_size, bit_info, ts, frame_type); + 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; - /* When progressive discard activated, just remove as few as possible - * just to make this frame in. - */ -#if PROGRESSIVE_DISCARD - /* The cases of seq-jump, out-of-order, and seq restart should have + /* 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)) - jb->jb_max_count + 1; pj_assert(distance > 0); -#else - distance = PJ_MAX(jb->jb_max_count/4, 1); -#endif + removed = jb_framelist_remove_head(&jb->jb_framelist, distance); status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame, - min_frame_size, bit_info, ts, frame_type); + min_frame_size, bit_info, ts, + PJMEDIA_JB_NORMAL_FRAME); jb->jb_discard += removed; } @@ -936,11 +1003,11 @@ PJ_DEF(void) pjmedia_jbuf_put_frame3(pjmedia_jbuf *jb, *discarded = (status != PJ_SUCCESS); if (status == PJ_SUCCESS) { - if (jb->jb_status == JB_STATUS_PREFETCHING) { + 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_status = JB_STATUS_PROCESSING; + jb->jb_prefetching = PJ_FALSE; } jb->jb_level += (new_size > cur_size ? new_size-cur_size : 1); jbuf_update(jb, JB_OP_PUT); @@ -983,7 +1050,7 @@ PJ_DEF(void) pjmedia_jbuf_get_frame3(pjmedia_jbuf *jb, pj_uint32_t *ts, int *seq) { - if (jb->jb_status == JB_STATUS_PREFETCHING) { + if (jb->jb_prefetching) { /* Can't return frame because jitter buffer is filling up * minimum prefetch. @@ -1030,7 +1097,7 @@ PJ_DEF(void) pjmedia_jbuf_get_frame3(pjmedia_jbuf *jb, } else { /* Jitter buffer is empty */ if (jb->jb_prefetch) - jb->jb_status = JB_STATUS_PREFETCHING; + jb->jb_prefetching = PJ_TRUE; //pj_bzero(frame, jb->jb_frame_size); *p_frame_type = PJMEDIA_JB_ZERO_EMPTY_FRAME; |