summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia/jbuf.c
blob: 1120fcdf1eff8981b20807b092351c136906bd4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
/* $Header: /pjproject-0.3/pjmedia/src/pjmedia/jbuf.c 8     10/14/05 12:23a Bennylp $ */

#include <pjmedia/jbuf.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <string.h>	/* memset() */

/*
 * At the current state, this is basicly an ugly jitter buffer.
 * It worked before by observing level, bit it doesn't work.
 * Then I used the size, which makes the level obsolete.
 * That's why it's ugly!
 */

#define MAX_SEQ_RANGE	1000	/* Range in which sequence is considered still within session */
#define UPDATE_DURATION	20	/* Number of frames retrieved before jitter is updated */

#define THIS_FILE   "jbuf"

/* Individual frame in the frame list. */ 
struct pj_jbframe
{
    pj_uint32_t  extseq;
    void	*buf;
};


/* Jitter buffer state. */ 
typedef enum jb_state_t
{
    JB_PREFETCH,
    JB_NORMAL,
} jb_state_t;


/* Jitter buffer last operation. */ 
typedef enum jb_op_t
{
    JB_PUT,
    JB_GET,
} jb_op_t;


/* Short name for convenience. */ 
typedef struct pj_jitter_buffer JB;


/* Initialize framelist. */ 
static pj_status_t
pj_framelist_init( pj_jbframelist *lst, pj_pool_t *pool, unsigned maxcount )
{
    PJ_LOG(5, (THIS_FILE, "..pj_frame_list_init [lst=%p], maxcount=%d", lst, maxcount));

    memset(lst, 0, sizeof(*lst));
    lst->maxcount = maxcount;
    lst->frames = pj_pool_calloc( pool, maxcount, sizeof(*lst->frames) );
    if (lst->frames == NULL) {
	PJ_LOG(1,(THIS_FILE, "Unable to allocate frame list!"));
	return -1;
    }
    return 0;    
}

/* Reset framelist. */
static void 
pj_framelist_reset( pj_jbframelist *lst )
{
    PJ_LOG(6, (THIS_FILE, "..pj_frame_list_reset [lst=%p]", lst));

    lst->count = 0;
    lst->head = 0;
    lst->frames[0].extseq = 0;
}

/* Put a buffer with the specified sequence into the ordered list. */ 
static int 
pj_framelist_put( pj_jbframelist *lst, pj_uint32_t extseq, void *buf )
{
    unsigned pos = (unsigned)-1;
    pj_uint32_t startseq = lst->frames[lst->head].extseq;

    if (lst->count == 0) {
	/* Empty list. Initialize frame list. */
	PJ_LOG(6, (THIS_FILE, "    ..pj_frame_list_put [lst=%p], empty, seq=%u@pos=%d", 
		   lst, extseq, lst->head));

	lst->head = 0;
	lst->count = 1;
	lst->frames[0].buf = buf;
	lst->frames[0].extseq = extseq;
	return 0;
	
    } else if (extseq < startseq) {
	/* The sequence number is lower than our oldest packet. This can mean
	   two things:
	    - old packet has been receieved, or
	    - the sequence number has wrapped around.
	 */  
	if (startseq + lst->maxcount <= extseq) {
	    /* The sequence number has wrapped around, but it is beyond
	       the capacity of the list (i.e. too soon).
	     */
	    PJ_LOG(5, (THIS_FILE, "    ..pj_frame_list_put TOO_SOON! [lst=%p] seq=%u, startseq=%d", 
		       lst, extseq, startseq));
	    return PJ_JB_STATUS_TOO_SOON;

	} else if (startseq-extseq > lst->maxcount && startseq+lst->maxcount > extseq) {
	    /* The sequence number has wrapped around, and it is still inside
	       the 'window' of the framelist.
	     */
	    pos = extseq - startseq;
	} else {
	    /* The new frame is too old. */
	    PJ_LOG(5, (THIS_FILE, "    ..pj_frame_list_put TOO_OLD! [lst=%p] seq=%u, startseq=%d", 
		       lst, extseq, startseq));
	    return PJ_JB_STATUS_TOO_OLD;
	}
	
    } else if (extseq > startseq + lst->maxcount) {
	/* Two possibilities here. Either:
	    - packet is really too soon, or
	    - sequence number of startseq has just wrapped around, and old packet
	      which hasn't wrapped is received.
	 */
	if (extseq < MAX_SEQ_RANGE /*approx 20 seconds with 50 fps*/) {
	    PJ_LOG(5, (THIS_FILE, "    ..pj_frame_list_put TOO_SOON! [lst=%p] seq=%u, startseq=%d", 
		       lst, extseq, startseq));
	    return PJ_JB_STATUS_TOO_SOON;
	} else {
	    PJ_LOG(5, (THIS_FILE, "    ..pj_frame_list_put TOO_OLD! [lst=%p] seq=%u, startseq=%d", 
		       lst, extseq, startseq));
	    return PJ_JB_STATUS_TOO_OLD;
	}
    } 

    /* The new frame is within the framelist capacity.
       Calculate position where to put it in the list.
     */
    if (pos == (unsigned)-1)
	pos = ((extseq - startseq) + lst->head) % lst->maxcount;

    pj_assert(pos < lst->maxcount);
    
    /* Update count only if we're not overwriting existing frame. */
    if (lst->frames[pos].buf == NULL)
        ++lst->count;

    lst->frames[pos].buf = buf;
    lst->frames[pos].extseq = extseq;

    PJ_LOG(6, (THIS_FILE, "    ..pj_frame_list_put [lst=%p] seq=%u, startseq=%d, head=%d, pos=%d", 
	       lst, extseq, startseq, lst->head, pos));
    return 0;
}

/* Get the first element of the list. */ 
static int 
pj_framelist_get( pj_jbframelist *lst, pj_uint32_t *extseq, void **buf )
{
    if (lst->count == 0) {
	/* Empty. */
	*buf = NULL;
	*extseq = 0;
	PJ_LOG(6, (THIS_FILE, "    ..pj_frame_list_get [lst=%p], empty!", lst));
	return -1;
	
    } else {
	*buf = lst->frames[lst->head].buf;
	*extseq = lst->frames[lst->head].extseq;
	lst->frames[lst->head].buf = NULL;

	PJ_LOG(6, (THIS_FILE, "    ..pj_frame_list_get [lst=%p] seq=%u, head=%d", 
		   lst, *extseq, lst->head));

	lst->head = (lst->head + 1) % lst->maxcount;
	--lst->count;
	return 0;
    }
}


/*****************************************************************************
 * Reset jitter buffer. 
 ****************************************************************************
*/
PJ_DEF(void) pj_jb_reset(JB *jb)
{
    PJ_LOG(6, (THIS_FILE, "pj_jb_reset [jb=%p]", jb));

    jb->level = jb->max_level = 1;
    jb->prefetch = jb->min;
    jb->get_cnt = 0;
    jb->lastseq = 0;
    jb->state = JB_PREFETCH;
    jb->upd_count = 0;
    jb->last_op = -1;
    pj_framelist_reset( &jb->lst );
}


/*****************************************************************************
 * Create jitter buffer.
 *****************************************************************************
 */ 
PJ_DEF(pj_status_t) pj_jb_init( pj_jitter_buffer *jb, pj_pool_t *pool, 
			        unsigned min, unsigned max, unsigned maxcount)
{
    pj_status_t status;

    if (maxcount <= max) {
	maxcount = max * 5 / 4;
	PJ_LOG(3,(THIS_FILE, "Jitter buffer maximum count was adjusted."));
    }

    jb->min = min;
    jb->max = max;

    status = pj_framelist_init( &jb->lst, pool, maxcount );
    if (status != PJ_SUCCESS)
	return status;

    pj_jb_reset(jb);

    PJ_LOG(4, (THIS_FILE, "pj_jb_init success [jb=%p], min=%d, max=%d, maxcount=%d", 
			  jb, min, max, maxcount));
    return PJ_SUCCESS;
}


/*****************************************************************************
 * Put a packet to the jitter buffer.
 *****************************************************************************
 */ 
PJ_DEF(pj_status_t) pj_jb_put( JB *jb, pj_uint32_t extseq, void *buf )
{
    unsigned distance;
    int status;
    
    PJ_LOG(6, (THIS_FILE, "==> pj_jb_put [jb=%p], seq=%u, buf=%p", jb, extseq, buf));

    if (jb->lastseq == 0)
	jb->lastseq = extseq - 1;

    /* Calculate distance between this packet and last received packet
       to detect long jump (indicating probably remote has just been
       restarted.
     */
    distance = (extseq > jb->lastseq) ? extseq - jb->lastseq : jb->lastseq - extseq;
    if (distance > MAX_SEQ_RANGE) {
	/* Distance is out of range, reset jitter while maintaining current jitter
	   level.
	 */
	int old_level = jb->level;
	int old_prefetch = jb->prefetch;

	PJ_LOG(4, (THIS_FILE, "    ..[jb=%p] distance out of range, resetting", jb));

	pj_jb_reset(jb);
	jb->level = old_level;
	jb->prefetch = old_prefetch;
	distance = 1;
	jb->lastseq = extseq - 1;
    }
    
    jb->lastseq = extseq;

    status = pj_framelist_put( &jb->lst, extseq, buf );
    if (status == PJ_JB_STATUS_TOO_OLD)
	return -1;

    if (status == PJ_JB_STATUS_TOO_SOON) {
	/* TODO: discard old packets.. */
	/* No, don't do it without putting a way to inform application so that
	   it can free the memory */
    }


    if (jb->last_op != JB_PUT) {
	if (jb->state != JB_PREFETCH)
	    jb->level--;
    } else {
	jb->level++;
    }

    if (jb->lst.count > jb->max_level)
	jb->max_level++;

    jb->last_op = JB_PUT;
    return 0;
}


/*
 * Update jitter buffer algorithm.
 */
static void jb_update(JB *jb, int apply, int log_info)
{
    unsigned abs_level = jb->max_level > 0 ? jb->max_level : -jb->max_level;
    unsigned new_prefetch;

    /* Update prefetch count */
    if (abs_level > jb->prefetch)
	new_prefetch = (jb->prefetch + abs_level*9 + 1) / 10;
    else {
	new_prefetch = (jb->prefetch*4 + abs_level) / 5;
	pj_assert(new_prefetch <= jb->prefetch);
    }

    if (log_info) {
	PJ_LOG(5, (THIS_FILE, "    ..jb_update [jb=%p], level=%d, max_level=%d, old_prefetch=%d, new_prefetch=%d", 
			      jb, jb->level, jb->max_level, jb->prefetch, new_prefetch));
    } else {
	PJ_LOG(6, (THIS_FILE, "    ..jb_update [jb=%p], level=%d, max_level=%d, old_prefetch=%d, new_prefetch=%d", 
			      jb, jb->level, jb->max_level, jb->prefetch, new_prefetch));
    }

    if (new_prefetch < jb->min) new_prefetch = jb->min;
    if (new_prefetch > jb->max) new_prefetch = jb->max;

    /* If jitter buffer is empty, set state to JB_PREFETCH, taking care of the
       new prefetch setting.
     */
    if (jb->lst.count == 0) {
	jb->state = JB_PREFETCH;
	jb->get_cnt = 0;
    } else {
	/* Check if delay is too long, which in this case probably better to
	   discard some frames..
	 */
	/* No, don't do it without putting a way to inform application so that
	   it can free the memory */
    }


    if (apply) {
	jb->prefetch = new_prefetch;
	if (jb->max_level > 0)
	    jb->max_level--;
    } else {
	jb->level = new_prefetch;
    }
}


/*****************************************************************************
 * Get the oldest frame from jitter buffer.
 *****************************************************************************
 */ 
PJ_DEF(pj_status_t) pj_jb_get( JB *jb, pj_uint32_t *extseq, void **buf )
{
    pj_status_t status;
    
    PJ_LOG(6, (THIS_FILE, "<== pj_jb_get [jb=%p]", jb));

    /*
     * Check whether we're ready to give frame. When we're in JB_PREFETCH state,
     * only give frames only when:
     *	- the buffer has enough frames in it (jb->list.count > jb->prefetch), OR
     *	- after 'prefetch' attempts, there's still no frame, which in this
     *	  case PJ_JB_STATUS_FRAME_NULL will be returned by the next check.
     */
    if (jb->state == JB_PREFETCH && jb->lst.count <= jb->prefetch && jb->get_cnt < jb->prefetch) {
	jb->get_cnt++;   
	jb->last_op = JB_GET;
	PJ_LOG(5, (THIS_FILE, "    ..[jb=%p] bufferring...", jb));
	return PJ_JB_STATUS_FRAME_NULL;
    }

    /* Attempt to get one frame from the list. */
    status = pj_framelist_get( &jb->lst, extseq, buf );
    if (status != 0) {
	PJ_LOG(6, (THIS_FILE, "    ..[jb=%p] no packet!", jb));
	status = jb->lst.count ? PJ_JB_STATUS_FRAME_MISSING : PJ_JB_STATUS_FRAME_NULL;
	jb_update(jb, 1, 0);
	return status;
    }

    /* Force state to NORMAL */
    jb->state = JB_NORMAL;

    /* Increase level only when last operation is GET.
     * This is to prevent level from increasing during silence period, which
     * no packets is receieved.
     */
    if (jb->last_op != JB_GET) {
	int apply;

	//jb->level++;
	jb->last_op = JB_GET;

	apply = (++jb->upd_count > UPDATE_DURATION);
	if (apply)
	    jb->upd_count = 0;

	jb_update(jb, apply, apply);
    }

    PJ_LOG(6, (THIS_FILE, "    ..[jb=%p] seq=%u, level=%d, prefetch=%d, size=%u, delay=%d", 
			  jb, *extseq, jb->level, jb->prefetch, jb->lst.count,
			  jb->lastseq - *extseq));
    return 0;
}