Ejemplo n.º 1
0
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_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;
}
Ejemplo n.º 2
0
PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool,
					      const char *name,
					      unsigned clock_rate,
					      unsigned samples_per_frame,
					      unsigned channel_count,
					      unsigned max_delay,
					      unsigned options,
					      pjmedia_delay_buf **p_b)
{
    pjmedia_delay_buf *b;
    pj_status_t status;

    PJ_ASSERT_RETURN(pool && samples_per_frame && clock_rate && channel_count &&
		     p_b, PJ_EINVAL);
    PJ_ASSERT_RETURN(options==0, PJ_EINVAL);

    PJ_UNUSED_ARG(options);

    if (!name) {
	name = "delaybuf";
    }

    b = PJ_POOL_ZALLOC_T(pool, pjmedia_delay_buf);

    pj_ansi_strncpy(b->obj_name, name, PJ_MAX_OBJ_NAME-1);

    b->samples_per_frame = samples_per_frame;
    b->channel_count = channel_count;
    b->ptime = samples_per_frame * 1000 / clock_rate / channel_count;
    if (max_delay < b->ptime)
	max_delay = PJ_MAX(DEFAULT_MAX_DELAY, b->ptime);

    b->max_cnt = samples_per_frame * max_delay / b->ptime;
    b->eff_cnt = b->max_cnt >> 1;
    b->recalc_timer = RECALC_TIME;

    /* Create circular buffer */
    status = pjmedia_circ_buf_create(pool, b->max_cnt, &b->circ_buf);
    if (status != PJ_SUCCESS)
	return status;

    /* Create WSOLA */
    status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, 1,
				  PJMEDIA_WSOLA_NO_FADING, &b->wsola);
    if (status != PJ_SUCCESS)
	return status;

    /* Finally, create mutex */
    status = pj_lock_create_recursive_mutex(pool, b->obj_name, 
					    &b->lock);
    if (status != PJ_SUCCESS)
	return status;

    *p_b = b;

    TRACE__((b->obj_name,"Delay buffer created"));

    return PJ_SUCCESS;
}
Ejemplo n.º 3
0
/* Start Session Timers */
static void start_timer(pjsip_inv_session *inv)
{
    const pj_str_t UPDATE = { "UPDATE", 6 };
    pjsip_timer *timer = inv->timer;
    pj_time_val delay = {0};

    pj_assert(inv->timer->active == PJ_TRUE);

    stop_timer(inv);

    inv->timer->use_update =
	    (pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL,
				      &UPDATE) == PJSIP_DIALOG_CAP_SUPPORTED);
    if (!inv->timer->use_update) {
	/* INVITE always needs SDP */
	inv->timer->with_sdp = PJ_TRUE;
    }

    pj_timer_entry_init(&timer->timer,
			1,		    /* id */
			inv,		    /* user data */
			timer_cb);	    /* callback */
    
    /* Set delay based on role, refresher or refreshee */
    if ((timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) ||
	(timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS))
    {
	/* Add refresher expire timer */
	pj_timer_entry_init(&timer->expire_timer,
			    REFRESHER_EXPIRE_TIMER_ID,	    /* id */
			    inv,			    /* user data */
			    timer_cb);			    /* callback */

	delay.sec = timer->setting.sess_expires;
	/* Schedule the timer */
	pjsip_endpt_schedule_timer(inv->dlg->endpt, &timer->expire_timer, 
				   &delay);

	/* Next refresh, the delay is half of session expire */
	delay.sec = timer->setting.sess_expires / 2;
    } else {
	/* Send BYE if no refresh received until this timer fired, delay
	 * is the minimum of 32 seconds and one third of the session interval
	 * before session expiration.
	 */
	delay.sec = timer->setting.sess_expires - 
		    timer->setting.sess_expires/3;
	delay.sec = PJ_MAX((long)timer->setting.sess_expires-32, delay.sec);
    }

    /* Schedule the timer */
    pjsip_endpt_schedule_timer(inv->dlg->endpt, &timer->timer, &delay);

    /* Update last refresh time */
    pj_gettimeofday(&timer->last_refresh);
}
Ejemplo n.º 4
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;

    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)));
	}
    }
}
Ejemplo n.º 5
0
/* Start Session Timers */
static void start_timer(pjsip_inv_session *inv)
{
    pjsip_timer *timer = inv->timer;
    pj_time_val delay = {0};

    pj_assert(inv->timer->active == PJ_TRUE);

    stop_timer(inv);

    pj_timer_entry_init(&timer->timer,
			1,		    /* id */
			inv,		    /* user data */
			timer_cb);	    /* callback */
    
    /* Set delay based on role, refresher or refreshee */
    if ((timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) ||
	(timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS))
    {
	/* Next refresh, the delay is half of session expire */
	delay.sec = timer->setting.sess_expires / 2;
    } else {
	/* Send BYE if no refresh received until this timer fired, delay
	 * is the minimum of 32 seconds and one third of the session interval
	 * before session expiration.
	 */
	delay.sec = timer->setting.sess_expires - 
		    timer->setting.sess_expires/3;
	delay.sec = PJ_MAX((long)timer->setting.sess_expires-32, delay.sec);
    }

    /* Schedule the timer */
    pjsip_endpt_schedule_timer(inv->dlg->endpt, &timer->timer, &delay);

    /* Update last refresh time */
    pj_gettimeofday(&timer->last_refresh);
}
Ejemplo n.º 6
0
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;
    }
}
Ejemplo n.º 7
0
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;
	    }

	    /* 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));
	}
    }

    /* 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;
	}

	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;
    }
}
Ejemplo n.º 8
0
static pj_status_t pj_vpx_encoder_open(vpx_private *vpx) {
    vpx_codec_flags_t flags = 0;
    /* XXX: use VPX_CODEC_USE_OUTPUT_PARTITION ? */
    const struct vpx_codec_iface *iface = &vpx_codec_vp8_cx_algo;
    struct vpx_codec_enc_cfg enccfg;
    int res;

    TRACE_((THIS_FILE, "vpx pj_vpx_encoder_open"));

    res = vpx_codec_enc_config_default(iface, &enccfg, 0);
    if (res != VPX_CODEC_OK) {
        PJ_LOG(1, (THIS_FILE, "Failed to get vpx default config : %s", vpx_codec_err_to_string(res)));
        return PJMEDIA_CODEC_EFAILED;
    }

    enccfg.g_w = vpx->param.enc_fmt.det.vid.size.w;
    enccfg.g_h = vpx->param.enc_fmt.det.vid.size.h;
    enccfg.g_timebase.num = vpx->param.enc_fmt.det.vid.fps.num;
    enccfg.g_timebase.den = vpx->param.enc_fmt.det.vid.fps.denum;
    //provide dummy value to initialize wrapper, values will be updated each _encode()
    vpx_img_wrap(&vpx->rawimg,
                 VPX_IMG_FMT_I420,
                 vpx->param.enc_fmt.det.vid.size.w,
                 vpx->param.enc_fmt.det.vid.size.h,
                 1,
                 NULL);

    enccfg.g_threads = number_of_threads(enccfg.g_w, enccfg.g_h, number_of_cores());
    PJ_LOG(4, (THIS_FILE, "Using %d threads for VPX encoding", enccfg.g_threads));

    enccfg.g_lag_in_frames = 0;
    enccfg.g_pass = VPX_RC_ONE_PASS;
    enccfg.rc_end_usage = VPX_CBR;
    enccfg.rc_target_bitrate = vpx->param.enc_fmt.det.vid.avg_bps / 1000; // in kbit/s
    enccfg.g_timebase.num = 1;
    enccfg.g_timebase.den = 90000;
    enccfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
    enccfg.rc_resize_allowed = 1;
    enccfg.rc_min_quantizer = 2;
    enccfg.rc_max_quantizer = 56;
    enccfg.rc_undershoot_pct = 100;
    enccfg.rc_overshoot_pct = 15;
    enccfg.rc_buf_initial_sz = 500;
    enccfg.rc_buf_optimal_sz = 600;
    enccfg.rc_buf_sz = 1000;
    enccfg.kf_mode = VPX_KF_AUTO;
    enccfg.kf_max_dist = 3000;

    vpx->rc_max_intra_target = PJ_MAX(300, enccfg.rc_buf_sz * 0.5 * enccfg.g_timebase.num / 10);

    res = vpx_codec_enc_init(&vpx->encoder, vpx_codec_vp8_cx(), &enccfg, flags);
    if (res != VPX_CODEC_OK) {
        PJ_LOG(1, (THIS_FILE, "Failed to init vpx encoder : %s", vpx_codec_err_to_string(res)));
        return PJMEDIA_CODEC_EFAILED;
    }

    vpx_codec_control(&vpx->encoder, VP8E_SET_STATIC_THRESHOLD, 1);
    vpx_codec_control(&vpx->encoder, VP8E_SET_CPUUSED, -6);   // XXX: test
    vpx_codec_control(&vpx->encoder, VP8E_SET_TOKEN_PARTITIONS, VP8_ONE_TOKENPARTITION);
    vpx_codec_control(&vpx->encoder, VP8E_SET_MAX_INTRA_BITRATE_PCT, vpx->rc_max_intra_target);
#ifdef VP8E_SET_SCREEN_CONTENT_MODE
    vpx_codec_control(&vpx->encoder, VP8E_SET_SCREEN_CONTENT_MODE, 0);
#endif
    vpx->enc_iter = NULL;

    vpx->enc_buf_size = vpx->enc_vafp.framebytes;
    vpx->enc_buf = pj_pool_alloc(vpx->pool, vpx->enc_buf_size);

    vpx->dec_buf_size = vpx->dec_vafp.framebytes;
    vpx->dec_buf = pj_pool_alloc(vpx->pool, vpx->dec_buf_size);

    return PJ_SUCCESS;
}
Ejemplo n.º 9
0
static pj_status_t pj_vpx_encoder_open(vpx_private *vpx) {
    vpx_codec_flags_t flags = 0;
    const struct vpx_codec_iface *iface = &vpx_codec_vp8_cx_algo;
    struct vpx_codec_enc_cfg enccfg;
    int cpu_speed, rc_max_intra_target;
    int res;

    PJ_LOG(4, (THIS_FILE, "vpx pj_vpx_encoder_open"));

    res = vpx_codec_enc_config_default(iface, &enccfg, 0);
    if (res != VPX_CODEC_OK) {
        PJ_LOG(1,
                (THIS_FILE, "Failed to get vpx default config : %s", vpx_codec_err_to_string(res)));
        return PJMEDIA_CODEC_EFAILED;
    }

    enccfg.g_w = vpx->param.enc_fmt.det.vid.size.w;
    enccfg.g_h = vpx->param.enc_fmt.det.vid.size.h;
    enccfg.g_timebase.num = vpx->param.enc_fmt.det.vid.fps.num;
    enccfg.g_timebase.den = vpx->param.enc_fmt.det.vid.fps.denum;
    //provide dummy value to initialize wrapper, values will be updated each _encode()
    vpx_img_wrap(&vpx->rawimg, VPX_IMG_FMT_I420,
            vpx->param.enc_fmt.det.vid.size.w, vpx->param.enc_fmt.det.vid.size.h,
            1, (unsigned char*)1);

    // Following config taken from webRTC project.
    // vpx seems to support more configurable/complex settings.
    // could be interesting to have a look, but for now, consider
    // webRTC settings as optimized for our needs
#if defined(PJ_HAS_UNISTD_H) && PJ_HAS_UNISTD_H!=0
    if (enccfg.g_w * enccfg.g_h > 704 * 576
            && sysconf(_SC_NPROCESSORS_CONF) > 1) {
        // 2 threads when larger than 4CIF
        enccfg.g_threads = 2;
    } else
#endif
    {
        enccfg.g_threads = 1;
    }
    enccfg.g_lag_in_frames = 0;
    enccfg.g_pass = VPX_RC_ONE_PASS;
    enccfg.rc_end_usage = VPX_CBR;
    enccfg.rc_target_bitrate = vpx->param.enc_fmt.det.vid.avg_bps / 1000; // in kbit/s
    enccfg.g_timebase.num = 1;
    enccfg.g_timebase.den = 90000;
    // TODO : need a setting for 2 following ?
    enccfg.g_error_resilient = 0;
    enccfg.rc_resize_allowed = 1;
    enccfg.rc_min_quantizer = 2;
    enccfg.rc_max_quantizer = 56;
    enccfg.rc_undershoot_pct = 100;
    enccfg.rc_overshoot_pct = 15;
    enccfg.rc_buf_initial_sz = 500;
    enccfg.rc_buf_optimal_sz = 600;
    enccfg.rc_buf_sz = 1000;
    // Not use feedback_mode
    enccfg.kf_mode = VPX_KF_AUTO;
    enccfg.kf_max_dist = 3000;

    /* scalePar = 0.5; maxFrameRate = 30fps*/
    /* Don't go below 3 times the per frame bandwidth. */
    rc_max_intra_target = PJ_MAX(300, enccfg.rc_buf_sz * 0.5 * 30 / 10);
    // for android cpu speed set to -12
    // TODO : adjust for other platform as done in webRTC
    cpu_speed = -12;
    //flags |= VPX_CODEC_USE_OUTPUT_PARTITION;
    res = vpx_codec_enc_init(&vpx->encoder, vpx_codec_vp8_cx(), &enccfg, flags);
    if (res != VPX_CODEC_OK) {
        PJ_LOG(1,
                (THIS_FILE, "Failed to init vpx encoder : %s", vpx_codec_err_to_string(res)));
        return PJMEDIA_CODEC_EFAILED;
    }
    vpx_codec_control(&vpx->encoder, VP8E_SET_STATIC_THRESHOLD, 1);
    vpx_codec_control(&vpx->encoder, VP8E_SET_CPUUSED, cpu_speed);
    vpx_codec_control(&vpx->encoder, VP8E_SET_TOKEN_PARTITIONS,
            VP8_ONE_TOKENPARTITION);
    vpx_codec_control(&vpx->encoder, VP8E_SET_NOISE_SENSITIVITY, 1);
    vpx_codec_control(&vpx->encoder, VP8E_SET_MAX_INTRA_BITRATE_PCT,
            rc_max_intra_target);
    vpx->enc_iter = NULL;

    vpx->enc_buf_size = vpx->enc_vafp.framebytes;
    vpx->enc_buf = pj_pool_alloc(vpx->pool, vpx->enc_buf_size);

    vpx->dec_buf_size = vpx->dec_vafp.framebytes;
    vpx->dec_buf = pj_pool_alloc(vpx->pool, vpx->dec_buf_size);

    return PJ_SUCCESS;
}
Ejemplo n.º 10
0
pj_status_t pjmedia_vid_codec_h263_apply_fmtp(
				pjmedia_vid_codec_param *param)
{
    if (param->dir & PJMEDIA_DIR_ENCODING) {
	pjmedia_vid_codec_h263_fmtp fmtp_loc, fmtp_rem;
	pjmedia_rect_size size = {0};
	unsigned mpi = 0;
	pjmedia_video_format_detail *vfd;
	pj_status_t status;

	vfd = pjmedia_format_get_video_format_detail(&param->enc_fmt,
						     PJ_TRUE);

	/* Get local param */
	// Local param should be fetched from "param->enc_fmt" instead of
	// "param->dec_fmtp".
	//status = pjmedia_vid_codec_parse_h263_fmtp(&param->dec_fmtp,
	//					   &fmtp_loc);
	//if (status != PJ_SUCCESS)
	//    return status;
	fmtp_loc.mpi_cnt = 1;
	fmtp_loc.mpi[0].size = vfd->size;
	fmtp_loc.mpi[0].val  = fps_to_mpi(&vfd->fps);

	/* Get remote param */
	status = pjmedia_vid_codec_parse_h263_fmtp(&param->enc_fmtp,
						   &fmtp_rem);
	if (status != PJ_SUCCESS)
	    return status;

	/* Negotiate size & MPI setting */
	if (fmtp_rem.mpi_cnt == 0) {
	    /* Remote doesn't specify MPI setting, send QCIF=1 */
	    size.w = 176;
	    size.h = 144;
	    mpi	   = 1;
	//} else if (fmtp_loc.mpi_cnt == 0) {
	//    /* Local MPI setting not set, just use remote preference. */
	//    size = fmtp_rem.mpi[0].size;
	//    mpi  = fmtp_rem.mpi[0].val;
	} else {
	    /* Both have preferences, let's try to match them */
	    unsigned i, j;
	    pj_bool_t matched = PJ_FALSE;
	    pj_uint32_t min_diff = 0xFFFFFFFF;
	    pj_uint32_t loc_sq, rem_sq, diff;

	    /* Find the exact size match or the closest size, then choose
	     * the highest MPI among the match/closest pair.
	     */
	    for (i = 0; i < fmtp_rem.mpi_cnt && !matched; ++i) {
		rem_sq = fmtp_rem.mpi[i].size.w * fmtp_rem.mpi[i].size.h;
		for (j = 0; j < fmtp_loc.mpi_cnt; ++j) {
		    /* See if we got exact match */
		    if (fmtp_rem.mpi[i].size.w == fmtp_loc.mpi[j].size.w &&
			fmtp_rem.mpi[i].size.h == fmtp_loc.mpi[j].size.h)
		    {
			size = fmtp_rem.mpi[i].size;
			mpi  = PJ_MAX(fmtp_rem.mpi[i].val,
				      fmtp_loc.mpi[j].val);
			matched = PJ_TRUE;
			break;
		    }

		    /* Otherwise keep looking for the closest match */
		    loc_sq = fmtp_loc.mpi[j].size.w * fmtp_loc.mpi[j].size.h;
		    diff = loc_sq>rem_sq? (loc_sq-rem_sq):(rem_sq-loc_sq);
		    if (diff < min_diff) {
			size = rem_sq<loc_sq? fmtp_rem.mpi[i].size :
					      fmtp_loc.mpi[j].size;
			mpi  = PJ_MAX(fmtp_rem.mpi[i].val,
				      fmtp_loc.mpi[j].val);
		    }
		}
	    }
	}

	/* Apply the negotiation result */
	vfd->size = size;
	vfd->fps.num = 30000;
	vfd->fps.denum = 1001 * mpi;
    }

    if (param->dir & PJMEDIA_DIR_DECODING) {
	/* Here we just want to find the highest resolution and the lowest MPI
	 * we support and set it as the decoder param.
	 */
	pjmedia_vid_codec_h263_fmtp fmtp;
	pjmedia_video_format_detail *vfd;
	pj_status_t status;
	
	status = pjmedia_vid_codec_parse_h263_fmtp(&param->dec_fmtp,
						   &fmtp);
	if (status != PJ_SUCCESS)
	    return status;

	vfd = pjmedia_format_get_video_format_detail(&param->dec_fmt,
						     PJ_TRUE);

	if (fmtp.mpi_cnt == 0) {
	    /* No resolution specified, lets just assume 4CIF=1! */
	    vfd->size.w = 704;
	    vfd->size.h = 576;
	    vfd->fps.num = 30000;
	    vfd->fps.denum = 1001;
	} else {
	    unsigned i, max_size = 0, max_size_idx = 0, min_mpi = 32;
	    
	    /* Get the largest size and the lowest MPI */
	    for (i = 0; i < fmtp.mpi_cnt; ++i) {
		if (fmtp.mpi[i].size.w * fmtp.mpi[i].size.h > max_size) {
		    max_size = fmtp.mpi[i].size.w * fmtp.mpi[i].size.h;
		    max_size_idx = i;
		}
		if (fmtp.mpi[i].val < min_mpi)
		    min_mpi = fmtp.mpi[i].val;
	    }

	    vfd->size = fmtp.mpi[max_size_idx].size;
	    vfd->fps.num = 30000;
	    vfd->fps.denum = 1001 * min_mpi;
	}
    }

    return PJ_SUCCESS;
}