From 2a7c23f6630314b2bee18066deb1442b8937671d Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Thu, 13 Oct 2011 09:02:41 +0000 Subject: Re #1378: - Implemented new algorithm for JB progressive discard. - Added new API and for setting JB discard algorithm at run-time. - Updated JB test for the new algorithm. git-svn-id: http://svn.pjsip.org/repos/pjproject/branches/1.x@3814 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/build/Jbtest.dat | 31 +++- pjmedia/include/pjmedia/config.h | 59 ++++++- pjmedia/include/pjmedia/jbuf.h | 63 +++++-- pjmedia/src/pjmedia/jbuf.c | 355 +++++++++++++++++++++++---------------- pjmedia/src/test/jbuf_test.c | 11 +- 5 files changed, 353 insertions(+), 166 deletions(-) diff --git a/pjmedia/build/Jbtest.dat b/pjmedia/build/Jbtest.dat index e41f861a..4f5a67fd 100644 --- a/pjmedia/build/Jbtest.dat +++ b/pjmedia/build/Jbtest.dat @@ -19,11 +19,11 @@ # # 3. Success conditions, started with '!', followed by condition name # and its maximum tolerable value, in frames unit. Recognized condition -# names are: burst, discard, lost, empty, delay. These conditions will -# be verified with jitter buffer statistics after all session test data -# are executed. +# names are: burst, discard, lost, empty, delay, delay_min. These +# conditions will be verified with jitter buffer statistics after all +# session test data are executed. # Example: -# !delay 10 <- maximum average delay of jbuf is 10 frames +# !delay 10 <- average delay of jbuf is 10 frames # # 4. Session test data, containing sequence of jitter buffer events, # an event is represented by a character as follow: @@ -295,7 +295,7 @@ PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG !discard 50 <- frames discarded for delay adaptation !lost 50 <- ticket #1188, normal frame after discarded frame is flagged 'lost' to align signal !empty 0 -!delay 25 <- average delay, JB is able to adapt the delay +!delay_min 2 <- minimum delay, JB is able to adapt the delay PPPPPPPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPPPP PPPPPPPPPP PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG @@ -311,6 +311,23 @@ PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG . = Fixed mode prefetch 5, with two empty events @@ -349,7 +366,7 @@ PGGGGPPPGPPGPPPGGPGG PGPGPPGGPPGGPPPGGGPG PGGGGPPPGPPGPPPGGPGG !discard 50 <- frames discarded for delay adaptation !lost 50 <- ticket #1188, normal frame after discarded frame is flagged 'lost' to align signal !empty 0 -!delay 20 <- average delay, twice of minimal prefetch +!delay_min 20 <- minimum delay, twice of minimal prefetch PPPPPPPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPPPP PPPPPPPPPP PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG @@ -369,7 +386,7 @@ PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG = Large PUT burst at beginning, then normal with burst level 10 and periodic burst spikes %adaptive 0 0 40 -!burst 10 +!burst 12 !discard 300 <- not so relevant for long period session with many delay adjustments needed (i.e: for first burst and periodic spikes) !lost 300 <- ticket #1188, normal frame after discarded frame is flagged 'lost' to align signal !empty 60 <- delay adjustment effect, as there is actually no drift diff --git a/pjmedia/include/pjmedia/config.h b/pjmedia/include/pjmedia/config.h index b0c3d6f7..c3ba7052 100644 --- a/pjmedia/include/pjmedia/config.h +++ b/pjmedia/include/pjmedia/config.h @@ -886,7 +886,64 @@ * Default: 5 seconds */ #ifndef PJMEDIA_STREAM_KA_INTERVAL -# define PJMEDIA_STREAM_KA_INTERVAL 5 +# define PJMEDIA_STREAM_KA_INTERVAL 5 +#endif + + +/** + * Minimum gap between two consecutive discards in jitter buffer, + * in milliseconds. + * + * Default: 200 ms + */ +#ifndef PJMEDIA_JBUF_DISC_MIN_GAP +# define PJMEDIA_JBUF_DISC_MIN_GAP 200 +#endif + + +/** + * Minimum burst level reference used for calculating discard duration + * in jitter buffer progressive discard algorithm, in frames. + * + * Default: 1 frame + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_MIN_BURST +# define PJMEDIA_JBUF_PRO_DISC_MIN_BURST 1 +#endif + + +/** + * Maximum burst level reference used for calculating discard duration + * in jitter buffer progressive discard algorithm, in frames. + * + * Default: 200 frames + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_MAX_BURST +# define PJMEDIA_JBUF_PRO_DISC_MAX_BURST 100 +#endif + + +/** + * Duration for progressive discard algotithm in jitter buffer to discard + * an excessive frame when burst is equal to or lower than + * PJMEDIA_JBUF_PRO_DISC_MIN_BURST, in milliseconds. + * + * Default: 2000 ms + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_T1 +# define PJMEDIA_JBUF_PRO_DISC_T1 2000 +#endif + + +/** + * Duration for progressive discard algotithm in jitter buffer to discard + * an excessive frame when burst is equal to or lower than + * PJMEDIA_JBUF_PRO_DISC_MAX_BURST, in milliseconds. + * + * Default: 10000 ms + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_T2 +# define PJMEDIA_JBUF_PRO_DISC_T2 10000 #endif diff --git a/pjmedia/include/pjmedia/jbuf.h b/pjmedia/include/pjmedia/jbuf.h index 18576ef9..f9468b72 100644 --- a/pjmedia/include/pjmedia/jbuf.h +++ b/pjmedia/include/pjmedia/jbuf.h @@ -48,7 +48,7 @@ PJ_BEGIN_DECL /** * Types of frame returned by the jitter buffer. */ -enum pjmedia_jb_frame_type +typedef enum pjmedia_jb_frame_type { PJMEDIA_JB_MISSING_FRAME = 0, /**< No frame because it's missing */ PJMEDIA_JB_NORMAL_FRAME = 1, /**< Normal frame is being returned */ @@ -56,19 +56,47 @@ enum pjmedia_jb_frame_type because JB is bufferring. */ PJMEDIA_JB_ZERO_EMPTY_FRAME = 3 /**< Zero frame is being returned because JB is empty. */ -}; +} pjmedia_jb_frame_type; /** - * @see pjmedia_jb_frame_type. + * Enumeration of jitter buffer discard algorithm. The jitter buffer + * continuously calculates the jitter level to get the optimum latency at + * any time and in order to adjust the latency, the jitter buffer may need + * to discard some frames. */ -typedef enum pjmedia_jb_frame_type pjmedia_jb_frame_type; +typedef enum pjmedia_jb_discard_algo +{ + /** + * Jitter buffer should not discard any frame, except when the jitter + * buffer is full and a new frame arrives, one frame will be discarded + * to make space for the new frame. + */ + PJMEDIA_JB_DISCARD_NONE = 0, + + /** + * Only discard one frame in at least 200ms when the latency is considered + * much higher than it should be. When the jitter buffer is full and a new + * frame arrives, one frame will be discarded to make space for the new + * frame. + */ + PJMEDIA_JB_DISCARD_STATIC, + + /** + * The discard rate is dynamically calculated based on actual parameters + * such as jitter level and latency. When the jitter buffer is full and + * a new frame arrives, one frame will be discarded to make space for the + * new frame. + */ + PJMEDIA_JB_DISCARD_PROGRESSIVE + +} pjmedia_jb_discard_algo; /** * This structure describes jitter buffer state. */ -struct pjmedia_jb_state +typedef struct pjmedia_jb_state { /* Setting */ unsigned frame_size; /**< Individual frame size, in bytes. */ @@ -89,13 +117,7 @@ struct pjmedia_jb_state unsigned lost; /**< Number of lost frames. */ unsigned discard; /**< Number of discarded frames. */ unsigned empty; /**< Number of empty on GET events. */ -}; - - -/** - * @see pjmedia_jb_state - */ -typedef struct pjmedia_jb_state pjmedia_jb_state; +} pjmedia_jb_state; /** @@ -113,7 +135,9 @@ typedef struct pjmedia_jbuf pjmedia_jbuf; /** * Create an adaptive jitter buffer according to the specification. If * application wants to have a fixed jitter buffer, it may call - * #pjmedia_jbuf_set_fixed() after the jitter buffer is created. + * #pjmedia_jbuf_set_fixed() after the jitter buffer is created. Also + * if application wants to alter the discard algorithm, which the default + * PJMEDIA_JB_DISCARD_PROGRESSIVE, it may call #pjmedia_jbuf_set_discard(). * * This function may allocate large chunk of memory to keep the frames in * the buffer. @@ -179,6 +203,19 @@ PJ_DECL(pj_status_t) pjmedia_jbuf_set_adaptive( pjmedia_jbuf *jb, unsigned max_prefetch); +/** + * Set the jitter buffer discard algorithm. The default discard algorithm, + * set in jitter buffer creation, is PJMEDIA_JB_DISCARD_PROGRESSIVE. + * + * @param jb The jitter buffer. + * @param algo The discard algorithm to be used. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_jbuf_set_discard(pjmedia_jbuf *jb, + pjmedia_jb_discard_algo algo); + + /** * Destroy jitter buffer instance. * diff --git a/pjmedia/src/pjmedia/jbuf.c b/pjmedia/src/pjmedia/jbuf.c index e5295869..db4491a2 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. */ @@ -81,6 +79,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) */ @@ -97,6 +100,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 */ @@ -124,9 +128,11 @@ struct pjmedia_jbuf '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 @@ -392,7 +398,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) { @@ -447,15 +453,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 { @@ -488,13 +511,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; @@ -542,6 +565,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; @@ -551,6 +598,7 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_reset(pjmedia_jbuf *jb) 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); @@ -562,11 +610,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, @@ -649,6 +699,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) { @@ -684,63 +864,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, @@ -759,95 +886,35 @@ PJ_DEF(void) pjmedia_jbuf_put_frame2(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 = 0, overflow_pct = 0; - - /* Calculating percentage of burst overflow */ - if (jb->jb_status == JB_STATUS_PROCESSING) { - burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level); - if (cur_size > (int)burst_level) - overflow_pct = (cur_size - burst_level) * 100 / burst_level; - } - - /* Deciding discard interval (aggressiveness) based on - * burst overflow percentage. - */ - if (burst_level <= 5 && overflow_pct < 200) { - /* Tolerate spikes on relatively small burst level */ - interval = 0; - } else if (overflow_pct >= 200) { - /* Overflow >= 200% */ - interval = 4; - } else if (overflow_pct >= 100) { - /* Overflow >= 100% */ - interval = 5; - } else if (overflow_pct >= 10) { - /* Overflow >= 10% */ - interval = 7; - } else { - /* Overflow < 10%, tolerable */ - interval = 0; - } - - /* 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, frame_type); + min_frame_size, bit_info, + 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, frame_type); + min_frame_size, bit_info, + PJMEDIA_JB_NORMAL_FRAME); jb->jb_discard += removed; } diff --git a/pjmedia/src/test/jbuf_test.c b/pjmedia/src/test/jbuf_test.c index 9cffa921..309459b0 100644 --- a/pjmedia/src/test/jbuf_test.c +++ b/pjmedia/src/test/jbuf_test.c @@ -43,7 +43,8 @@ typedef struct test_cond_t { int discard; int lost; int empty; - int delay; /**< Maximum delay, in frames. */ + int delay; /**< Average delay, in frames. */ + int delay_min; /**< Minimum delay, in frames. */ } test_cond_t; static pj_bool_t parse_test_headers(char *line, test_param_t *param, @@ -69,6 +70,8 @@ static pj_bool_t parse_test_headers(char *line, test_param_t *param, cond->burst = cond_val; else if (pj_ansi_stricmp(cond_st, "delay") == 0) cond->delay = cond_val; + else if (pj_ansi_stricmp(cond_st, "delay_min") == 0) + cond->delay_min = cond_val; else if (pj_ansi_stricmp(cond_st, "discard") == 0) cond->discard = cond_val; else if (pj_ansi_stricmp(cond_st, "empty") == 0) @@ -217,6 +220,7 @@ int jbuf_main(void) cond.burst = -1; cond.delay = -1; + cond.delay_min = -1; cond.discard = -1; cond.empty = -1; cond.lost = -1; @@ -313,6 +317,11 @@ int jbuf_main(void) cond.delay, state.avg_delay/JB_PTIME); rc |= 2; } + if (cond.delay_min >= 0 && (int)state.min_delay/JB_PTIME > cond.delay_min) { + printf("! 'Minimum delay' should be %d, it is %d\n", + cond.delay_min, state.min_delay/JB_PTIME); + rc |= 32; + } if (cond.discard >= 0 && (int)state.discard > cond.discard) { printf("! 'Discard' should be %d, it is %d\n", cond.discard, state.discard); -- cgit v1.2.3