/* * This callback is executed when the time to send the next * receiver report is reached. */ UT_STATIC void rtcp_receive_report_timeout_cb (const vqec_event_t * const evptr, int32_t fd, int16_t event, void *arg) { rtp_session_t *p_sess = NULL; rtp_member_t *local_member = NULL; uint64_t time; struct timeval tv; if (!arg) { rtcp_tmevt_log_err("%s %s", __FUNCTION__, s_arg_is_null); /* ignore return {nothing to be done if stop fails} */ (void)vqec_event_stop(evptr); return; } p_sess = (rtp_session_t *)arg; local_member = p_sess->rtp_local_source; if (!local_member) { rtcp_tmevt_log_err("%s %s", __FUNCTION__, s_src_is_null); /* ignore return nothing to be done if stop fails */ (void)vqec_event_stop(evptr); return; } /* Update the timestamps */ time = TIME_GET_A(msec, get_sys_time()); local_member->rtp_timestamp = (uint32_t)time; local_member->ntp_timestamp = abs_time_to_ntp(msec_to_abs_time(time)); MCALL(p_sess, rtp_update_stats, TRUE); MCALL(p_sess, rtp_session_timeout_transmit_report); timerclear(&tv); rel_time_t diff = TIME_SUB_A_A(p_sess->next_send_ts, msec_to_abs_time(time)); /* If the rel-time delta is -ive [lagging behind] use 0. */ if (TIME_CMP_R(lt, diff, REL_TIME_0)) { diff = REL_TIME_0; } tv = TIME_GET_R(timeval, diff); /* ignore return no recourse if start fails*/ (void)vqec_event_start(evptr, &tv); }
const char * rel_time_to_str(rel_time_t t, char * buff, uint32_t buff_len) { boolean negative = TIME_CMP_R(lt, t, REL_TIME_0); size_t snprintf_result; if (! buff || ! buff_len) return NULL; if (negative) t = TIME_NEG_R(t); struct timeval tv = TIME_GET_R(timeval, t); snprintf_result = snprintf(buff, buff_len, "%s%ld.%06ld", negative ? "-" : "", tv.tv_sec, tv.tv_usec); ASSERT(snprintf_result > 0, "Bad snprintf result\n"); return buff; }
/****************************************************************************** * Adjust nll's state / predict a receive time for the given sample. ******************************************************************************/ void vqec_nll_adjust (vqec_nll_t * nll, abs_time_t actual_time, uint32_t pcr32, rel_time_t est_rtp_delta, boolean * disc, abs_time_t * predicted_time) { rel_time_t time_delta = REL_TIME_0, correction = REL_TIME_0, arrival_error; abs_time_t pred_arrival, prev_pred_base; boolean reset_base; if (!nll || !disc || !predicted_time) { VQEC_DP_DEBUG(VQEC_DP_DEBUG_NLL, "adjust() invalid inputs %p/%p/%p\n", nll, disc, predicted_time); if (predicted_time) { *predicted_time = get_sys_time(); } return; } prev_pred_base = nll->pred_base; if (*disc) { nll->num_exp_disc++; VQEC_DP_DEBUG(VQEC_DP_DEBUG_NLL, "nll(%p) explicit discontinuity signaled: " "actual_time %llu, pcr32 %u, est_rtp %llu\n", nll, TIME_GET_A(msec, actual_time), pcr32, TIME_GET_R(usec, est_rtp_delta)); } /* * The NLL operates in two modes: non-tracking and tracking. In * non-tracking mode, the original receive times of the samples are * unknown, and therefore, it just uses the sender RTP timestamps to * determine the send time for packets. In tracking mode, the receive * times of samples are known, except, e.g., when packets are received * of order. */ switch (nll->mode) { case VQEC_NLL_MODE_NONTRACKING: /* non-tracking mode */ if (!nll->got_first) { /* * if this is the 1st sample, set pred_base = actual_time if * actual_time is non-0, otherwise set it to current time. */ nll->got_first = TRUE; *disc = TRUE; if (!IS_ABS_TIME_ZERO(actual_time)) { nll->pred_base = actual_time; } else { nll->pred_base = get_sys_time(); } } else { /* else-of !nll->got_first */ /* * the code below considers the condition delta(rtp) >100 msecs * an "Implicit discontinuity". Whenever there the code detects * that the timing may be discontinuous, or the caller sets the * discontinuity flag, "est_rtp_delta" is added to the previous * value of pred_base. No limits are imposed on "est_rtp_delta". */ if (!*disc) { time_delta = vqec_nll_rtp_delta(nll->pcr32_base, pcr32); } if (*disc || TIME_CMP_R(gt, time_delta, MAX_NLL_DISCONTINUITY_THRESHOLD) || TIME_CMP_R(gt, TIME_NEG_R(time_delta), MAX_NLL_DISCONTINUITY_THRESHOLD)) { nll->pred_base = TIME_ADD_A_R(nll->pred_base, est_rtp_delta); if (!*disc) { nll->num_imp_disc++; /* implicitly discontinuous oper */ *disc = TRUE; VQEC_DP_DEBUG(VQEC_DP_DEBUG_NLL, "Implicit discontinuity : " "nll(%p) pcr %u old_pcr %u pred_base %llu\n", nll, pcr32, nll->pcr32_base, TIME_GET_A(msec, nll->pred_base)); } } else { /* else-of (*disc || TIME_CMP_R(... */ nll->pred_base = TIME_ADD_A_R(nll->pred_base, time_delta); } } /* !nll->got_first */ /* * Protect backward fold in predicted time; the previous predicted * time is selected if this condition is true. */ if (TIME_CMP_A(lt, nll->pred_base, prev_pred_base)) { VQEC_DP_DEBUG(VQEC_DP_DEBUG_NLL, "Prediction in past: nll(%p)" "new_base = %llu old_base = %llu pcr32 %u\n", nll, TIME_GET_A(msec, nll->pred_base), TIME_GET_A(msec, prev_pred_base), nll->pcr32_base); nll->pred_base = prev_pred_base; nll->predict_in_past++; } nll->pcr32_base = pcr32; *predicted_time = nll->pred_base; if (nll->switch_to_tracking) { /* switch to tracking mode */ /* * The rcc-repair burst and primary streams are received * somewhat "asynchronously" of each other, i.e., the primary * packets may be received at any epoch within the join latency * window. Because of the bandwidth / join skew, in tracking * mode the arrival error should be computed from the first * primary packet's actual receive time. However, the time * skew between the predicted receive time of the 1st primary, * and it's actual receive time must be preserved, and added to * all subsequent predictions in tracking mode. This value is * termed "primary_offset". To switch to tracking mode it is * *necessary* that a packet with non-0 actual time is provided. * The error case is explicitly handled by using current predicted * base as actual time causing primary_offset to be 0. This concept * of time-shift is now an integral part of the nll, as is * computed / cached implicitly when switching to tracking mode. */ abs_time_t act; if (IS_ABS_TIME_ZERO(actual_time)) { act = nll->pred_base; } else { act = actual_time; } nll->primary_offset = TIME_SUB_A_A(nll->pred_base, act); nll->pred_base = nll->last_actual_time = act; nll->switch_to_tracking = FALSE; nll->mode = VQEC_NLL_MODE_TRACKING; VQEC_DP_DEBUG(VQEC_DP_DEBUG_NLL, "Switch to tracking : nll(%p) pred_base %llu " "primary offset %lld actual time %llu \n", nll, TIME_GET_A(msec, nll->pred_base), TIME_GET_R(usec, nll->primary_offset), TIME_GET_A(msec, actual_time)); } break; /* done with non-tracking processing */ case VQEC_NLL_MODE_TRACKING: /* tracking mode */ if (!nll->got_first) { /* * if this is the 1st sample, set pred_base to actual_time + * non-tracking-offset if actual time is non-0; otherwise, set it to * current time + non-tracking-offset. */ nll->got_first = TRUE; *disc = TRUE; if (!IS_ABS_TIME_ZERO(actual_time)) { nll->pred_base = actual_time; } else { nll->pred_base = get_sys_time(); } } else { /* else-of !nll->got_first */ reset_base = FALSE; if (!*disc) { time_delta = vqec_nll_rtp_delta(nll->pcr32_base, pcr32); /* * We leave open the possibility of bounding this delta * prior computing arrival error, and instead using the * receive timestamp delta estimate (as for explicit disc) * in an attempt to better predict implicit discontinuities. * (this can also be done if the arrival error exceeded * threshold, but there was no explicit discontinuity). */ } else { /* * In case of explicit discontinuity, approximate rtp delta from * the receive timestamps of the current & previous sample, if * they are both non-0. This approx is used only If the delta is * < DISCONTINUITY_THRESHOLD msecs. Otherwise we'll reset * the nll's pred_base / error average. */ if (!IS_ABS_TIME_ZERO(actual_time) && (!IS_ABS_TIME_ZERO(nll->last_actual_time))) { time_delta = TIME_SUB_A_A(actual_time, nll->last_actual_time); if (TIME_CMP_R(gt, time_delta, MAX_NLL_DISCONTINUITY_THRESHOLD) || TIME_CMP_R(gt, TIME_NEG_R(time_delta), MAX_NLL_DISCONTINUITY_THRESHOLD)) { reset_base = TRUE; } /* (TIME_CMP_R(gt...) */ } /* (!IS_ABS_TIME_ZERO()... */ } /* (!*disc) */ /* * If we have a bounded estimate of the sender's time delta, * compute arrival error, and ensure that it is bounded by * +/- MAX_ARRIVAL_ERROR msecs. If not, we'll reset the nll's * pred_base / error average. If no actual time is provided, * we use, actual_time = pred_base + rtp_delta = pred_arrival. */ if (!reset_base) { abs_time_t act; if (IS_ABS_TIME_ZERO(actual_time)) { act = TIME_ADD_A_R(nll->pred_base, time_delta); } else { act = actual_time; } pred_arrival = TIME_ADD_A_R(nll->pred_base, time_delta); arrival_error = TIME_SUB_A_A(pred_arrival, act); if (TIME_CMP_R(lt, arrival_error, MAX_ARRIVAL_ERROR) && TIME_CMP_R(lt, TIME_NEG_R(arrival_error), MAX_ARRIVAL_ERROR)) { correction = vqec_nll_update_error(nll, arrival_error); nll->pred_base = TIME_ADD_A_R(pred_arrival, correction); } else { reset_base = TRUE; /* arrival error is out-of-bound */ } } if (reset_base) { /* explicit disc or excessive error */ if (!*disc) { nll->num_imp_disc++; /* implicit discontinuous oper */ *disc = TRUE; } /* * We must have an actual time provided for this particular * case. If one is not provided, the last pred_base is used; * a counter is incremented to keep track of such rather * unlikely incidents. */ if (!IS_ABS_TIME_ZERO(actual_time)) { nll->pred_base = actual_time; } else { nll->reset_base_no_act_time++; VQEC_DP_DEBUG(VQEC_DP_DEBUG_NLL, "Reset base w/o act time " "nll(%p)", nll); } nll->error_avg = REL_TIME_0; /* reset error average */ VQEC_DP_DEBUG(VQEC_DP_DEBUG_NLL, "Tracking mode discontinuity : " "nll(%p) imp_disc %u exp_disc %u pcr %u old_pcr %u " "pred_base %llu\n", nll, nll->num_imp_disc, nll->num_exp_disc, pcr32, nll->pcr32_base, TIME_GET_A(msec, nll->pred_base)); } /* end-of (reset_base) */ } /* end-of (!nll->got_first) */ /* * Protect backward fold in predicted time; the previous predicted * time is selected if this condition is true. */ if (TIME_CMP_A(lt, nll->pred_base, prev_pred_base)) { VQEC_DP_DEBUG(VQEC_DP_DEBUG_NLL, "Prediction in past: nll(%p)" "new_base = %llu old_base = %llu pcr32 %u\n", nll, TIME_GET_A(msec, nll->pred_base), TIME_GET_A(msec, prev_pred_base), nll->pcr32_base); nll->pred_base = prev_pred_base; nll->predict_in_past++; } nll->pcr32_base = pcr32; nll->last_actual_time = actual_time; *predicted_time = TIME_ADD_A_R(nll->pred_base, nll->primary_offset); break; /* done with tracking processing */ default: /* not reached */ VQEC_DP_ASSERT_FATAL(0, "nll fatal error"); break; } /* end switch(mode) */ if (VQEC_DP_GET_DEBUG_FLAG(VQEC_DP_DEBUG_COLLECT_STATS)) { nll->num_obs++; } VQEC_DP_DEBUG(VQEC_DP_DEBUG_NLL_ADJUST, "Adjustment: nll(%p), base %llu, " "pcr32 %u, correction %lld, cum err %lld\n", nll, TIME_GET_A(msec, nll->pred_base), nll->pcr32_base, TIME_GET_R(usec, correction), TIME_GET_R(usec, nll->error_avg)); }