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; }
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; }
/* 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); }
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))); } } }
/* 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); }
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; } }
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; } }
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; }
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; }
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(¶m->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(¶m->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(¶m->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(¶m->dec_fmtp, &fmtp); if (status != PJ_SUCCESS) return status; vfd = pjmedia_format_get_video_format_detail(¶m->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; }