1. 简介:
本文主要描述gstreamer中rtpjitterbuffer的定时器线程的处理流程,定时器主要对丢包进行延迟处理。
2. 流程:
2.1 定时器线程主要流程:
1) 当rtpjitterbuffer组件状态从READY升至PAUSED时,会创建出定时器的子线程。
2) 从当前定时器中找到超时时间最早的定时器。
3) 如果没有找到定时器,说明当前并没有定时器被添加,此时会挂起本线程,等待主线程上丢包后添加相应的定时器。
4) 找到定时器后,与当前时间比较,如果已经超时,则处理超时事件,处理超时事件完成后回到4)。
5) 如果这个定时器还没有超时,说明所有定时器都还没有到,本线程进入睡眠,待睡眠醒来时,回到2)。
流程如图:
2.2 超时重传处理流程:
这里主要分析单个包丢失后的处理流程。
1) 当一个不是预期的包到来,且序号大于预期包,则认为预期包可能丢包,这时候进入丢包处理流程。
2) 首先通过函数calculate_expected计算第一次超时的时间,这里使用的是预期包和实际到达包的序号差和dts的比值,线性增大的时间戳作为超时时间,具体见下分析。
3) 为对应包序添加定时器,此时非预期的包照常处理,依旧进入jbuf,但是由于包序不连续,推送线程不会把数据往下游发送。
4) 定时器线程等待超时时间的到来。
5) 如果超时到来之前,预期包到达,则删除定时器,进入正常的包处理流程,定时器线程唤醒后没有对应的定时器事件,则进行其他处理。
6) 如果超时到来之前,包都没有到达,则定时器触发,需要判断当前重试次数是否已经达到上限,或者重试时间超过限制的总时间。
7) 如果尝试满了,则将包设置为LOST,在下一个超时后执行do_lost_timeout操作,将该序号的包置位LOST的event向下游发送。
8) 如果还可以继续尝试,则刷新当前尝试次数和尝试时间,重新调度定时器,再次加入调度。
流程如图:
3. 代码分析:
3.1 主入口函数wait_next_timeout
当rtpjitterbuffer从READY状态转换到PAUSED状态时,会创建一个子线程用来对所有的定时器事件进行管理。
其代码如下,虽然比较冗长,但是处理流程比较简单,如上描述。
/* called when we need to wait for the next timeout. * * We loop over the array of recorded timeouts and wait for the earliest one. * When it timed out, do the logic associated with the timer. * * If there are no timers, we wait on a gcond until something new happens. */ static void wait_next_timeout (GstRtpJitterBuffer * jitterbuffer) { GstRtpJitterBufferPrivate *priv = jitterbuffer->priv; GstClockTime now = 0; JBUF_LOCK (priv); while (priv->timer_running) { TimerData *timer = NULL; GstClockTime timer_timeout = -1; gint i, len; /* If we have a clock, update "now" now with the very * latest running time we have. If timers are unscheduled below we * otherwise wouldn't update now (it's only updated when timers * expire), and also for the very first loop iteration now would * otherwise always be 0 */ // 获取当前时间,主要用来和定时器的超时时间做对比。 GST_OBJECT_LOCK (jitterbuffer); if (GST_ELEMENT_CLOCK (jitterbuffer)) { now = gst_clock_get_time (GST_ELEMENT_CLOCK (jitterbuffer)) - GST_ELEMENT_CAST (jitterbuffer)->base_time; } GST_OBJECT_UNLOCK (jitterbuffer); GST_DEBUG_OBJECT (jitterbuffer, "now %" GST_TIME_FORMAT, GST_TIME_ARGS (now)); len = priv->timers->len; // 遍历所有定时器,找到其中时间最小的。 for (i = 0; i < len; i++) { TimerData *test = &g_array_index (priv->timers, TimerData, i); GstClockTime test_timeout = get_timeout (jitterbuffer, test); gboolean save_best = FALSE; GST_DEBUG_OBJECT (jitterbuffer, "%d, %d, %d, %" GST_TIME_FORMAT, i, test->type, test->seqnum, GST_TIME_ARGS (test_timeout)); /* find the smallest timeout */ if (timer == NULL) { // 当前没有最小值 save_best = TRUE; } else if (timer_timeout == -1) { // 不是很确定什么场景会有超时时间为-1的,因此这两个分支不分析。 /* we already have an immediate timeout, the new timer must be an * immediate timer with smaller seqnum to become the best */ if (test_timeout == -1 && (gst_rtp_buffer_compare_seqnum (test->seqnum, timer->seqnum) > 0)) save_best = TRUE; } else if (test_timeout == -1) { /* first immediate timer */ save_best = TRUE; } else if (test_timeout < timer_timeout) { /* earlier timer */ // 需要更早触发的定时器。 save_best = TRUE; } else if (test_timeout == timer_timeout && (gst_rtp_buffer_compare_seqnum (test->seqnum, timer->seqnum) > 0)) { /* same timer, smaller seqnum */ // 相同的超时时间,选择包序号小的。 save_best = TRUE; } // 保存最小的超时事件。 if (save_best) { GST_DEBUG_OBJECT (jitterbuffer, "new best %d", i); timer = test; timer_timeout = test_timeout; } } if (timer && !priv->blocked) { GstClock *clock; GstClockTime sync_time; GstClockID id; GstClockReturn ret; GstClockTimeDiff clock_jitter; // 时间到了,需要处理对应事件,处理完后继续找最小的超时事件,依次处理。 // 直到所有定时器的超时事件都在now之后。 if (timer_timeout == -1 || timer_timeout <= now) { do_timeout (jitterbuffer, timer, now); /* check here, do_timeout could have released the lock */ if (!priv->timer_running) break; continue; } GST_OBJECT_LOCK (jitterbuffer); clock = GST_ELEMENT_CLOCK (jitterbuffer); if (!clock) { GST_OBJECT_UNLOCK (jitterbuffer); /* let's just push if there is no clock */ GST_DEBUG_OBJECT (jitterbuffer, "No clock, timeout right away"); now = timer_timeout; continue; } // 添加对应的定时器,进入睡眠直到对应的时间到来。 /* prepare for sync against clock */ sync_time = timer_timeout + GST_ELEMENT_CAST (jitterbuffer)->base_time; /* add latency of peer to get input time */ sync_time += priv->peer_latency; GST_DEBUG_OBJECT (jitterbuffer, "sync to timestamp %" GST_TIME_FORMAT " with sync time %" GST_TIME_FORMAT, GST_TIME_ARGS (timer_timeout), GST_TIME_ARGS (sync_time)); /* create an entry for the clock */ id = priv->clock_id = gst_clock_new_single_shot_id (clock, sync_time); priv->timer_timeout = timer_timeout; priv->timer_seqnum = timer->seqnum; GST_OBJECT_UNLOCK (jitterbuffer); /* release the lock so that the other end can push stuff or unlock */ JBUF_UNLOCK (priv); ret = gst_clock_id_wait (id, &clock_jitter); JBUF_LOCK (priv); if (!priv->timer_running) { gst_clock_id_unref (id); priv->clock_id = NULL; break; } // UNSCHEDLED表示在定时之外被唤醒,例如当前监听的定时器事件被移除。 // 通过调用函数unschedule_current_timer会触发相应的定时器时间。 if (ret != GST_CLOCK_UNSCHEDULED) { now = timer_timeout + MAX (clock_jitter, 0); GST_DEBUG_OBJECT (jitterbuffer, "sync done, %d, #%d, %" GST_STIME_FORMAT, ret, priv->timer_seqnum, GST_STIME_ARGS (clock_jitter)); } else { GST_DEBUG_OBJECT (jitterbuffer, "sync unscheduled"); } /* and free the entry */ gst_clock_id_unref (id); priv->clock_id = NULL; } else { // 没有定时器,等待主线程添加定时器事件。 /* no timers, wait for activity */ JBUF_WAIT_TIMER (priv); } } JBUF_UNLOCK (priv); GST_DEBUG_OBJECT (jitterbuffer, "we are stopping"); return; }
3.2 小范围丢包
丢包主要有三个处理函数,第一次发现包丢失,计算第一次超时calculate_expected,超时到来处理函数do_expected_timeout,确认丢包处理函数do_lost_timeout。
3.2.1 第一次触发
第一次触发超时时,会根据当前收到的包的dts和上一个有效的dts值计算总的时间间隔和平均每个包的时间间隔。
如果丢包的范围过大,会与参数latency_ns进行对比,仅保留latency_ns时间窗口内的包等待延迟的到来或者重传,窗口外的包会直接标记为丢包。
第一次超时的时间是以上一次有效dts为基准,每个包预计到达时间为包序差乘以平均时间间隔加上上一次有效dts。
static void calculate_expected (GstRtpJitterBuffer * jitterbuffer, guint32 expected, guint16 seqnum, GstClockTime dts, gint gap) { GstRtpJitterBufferPrivate *priv = jitterbuffer->priv; GstClockTime total_duration, duration, expected_dts; TimerType type; GST_DEBUG_OBJECT (jitterbuffer, "dts %" GST_TIME_FORMAT ", last %" GST_TIME_FORMAT, GST_TIME_ARGS (dts), GST_TIME_ARGS (priv->last_in_dts)); if (dts == GST_CLOCK_TIME_NONE) { GST_WARNING_OBJECT (jitterbuffer, "Have no DTS"); return; } /* the total duration spanned by the missing packets */ // 计算丢包的总间隔,如包1,dts->1s,接着收到包5,dts->5s // 则总的时间间隔为4s,gap为3(期望收到的包seqnum为2,包差为5-2=3) // 平均时间间隔为4 / 3 // 这里计算认为包间隔均相同 if (dts >= priv->last_in_dts) total_duration = dts - priv->last_in_dts; else total_duration = 0; /* interpolate between the current time and the last time based on * number of packets we are missing, this is the estimated duration * for the missing packet based on equidistant packet spacing. */ duration = total_duration / (gap + 1); GST_DEBUG_OBJECT (jitterbuffer, "duration %" GST_TIME_FORMAT, GST_TIME_ARGS (duration)); // 包的间隔太大,超过了参数latency_ns(包在rtpjitterbuffer中保存的最长时间) // 即认为,如果包间隔估计没有错误,收到这个包序前需要接收的所有包的总时间已经超过 // 这个包能保存的最大时间,这时候会丢弃一部分数据,在时间窗口内尽量多的接收数据 if (total_duration > priv->latency_ns) { GstClockTime gap_time; guint lost_packets; // last receive // | | latency_ns | // | gap_time |<--------------------------------------------------->| // | lost_packets | | // | LOST | EXPECTED | // |----------------------------------------------------------------------------| // | // new expected // new last_in_dts if (duration > 0) { GstClockTime gap_dur = gap * duration; if (gap_dur > priv->latency_ns) gap_time = gap_dur - priv->latency_ns; else gap_time = 0; lost_packets = gap_time / duration; } else { gap_time = total_duration - priv->latency_ns; lost_packets = gap; } /* too many lost packets, some of the missing packets are already * too late and we can generate lost packet events for them. */ GST_DEBUG_OBJECT (jitterbuffer, "lost packets (%d, #%d->#%d) duration too large %" GST_TIME_FORMAT " > %" GST_TIME_FORMAT ", consider %u lost (%" GST_TIME_FORMAT ")", gap, expected, seqnum - 1, GST_TIME_ARGS (total_duration), GST_TIME_ARGS (priv->latency_ns), lost_packets, GST_TIME_ARGS (gap_time)); /* this timer will fire immediately and the lost event will be pushed from * the timer thread */ if (lost_packets > 0) { add_timer (jitterbuffer, TIMER_TYPE_LOST, expected, lost_packets, priv->last_in_dts + duration, 0, gap_time); expected += lost_packets; priv->last_in_dts += gap_time; } } // 以平均时间间隔作为首次超时时间。 expected_dts = priv->last_in_dts + duration; // 如果需要发送重传请求,则标记该事件为EXPECTED,否则直接标记为LOST。 if (priv->do_retransmission) { TimerData *timer; type = TIMER_TYPE_EXPECTED; /* if we had a timer for the first missing packet, update it. */ if ((timer = find_timer (jitterbuffer, type, expected))) { GstClockTime timeout = timer->timeout; timer->duration = duration; if (timeout > (expected_dts + timer->rtx_retry)) { GstClockTime delay = timeout - expected_dts - timer->rtx_retry; reschedule_timer (jitterbuffer, timer, timer->seqnum, expected_dts, delay, TRUE); } expected++; expected_dts += duration; } } else { type = TIMER_TYPE_LOST; } // 给所有丢包的序号都添加相应的定时器 // 超时时间按序增加一个平均时间间隔 // P1 -> T1 + duration * (1 - 1) // P2 -> T1 + duration * (2 - 1) // P3 -> T1 + duration * (3 - 1) // ... // PN -> T1 + duration * (N - 1) while (gst_rtp_buffer_compare_seqnum (expected, seqnum) > 0) { add_timer (jitterbuffer, type, expected, 0, expected_dts, 0, duration); expected_dts += duration; expected++; } }
3.2 定时器超时仍未收到对应包
这个函数整体流程比较简单,但是里面涉及了比较多的时间计算,这里一个一个展开描述。
1) rtx_retry_timeout: 局部变量,指本次的超时时间,计算主要根据rtpjitterbuffer中的rtx_retry_timeout参数和rtx_min_retry_timeout参数。
当rtx_retry_timeout没有设置时候,会根据avg_rtx_rtt参数和avg_jitter参数进行计算,如果没有rtt参数,则使用默认值。
当rtx_min_retry_timeout没有设置时候,会根据packet_spacing参数进行计算,packet_spacing在收到连续的包时候,会调用calculate_packet_spacing函数进行计算。
2) rtx_retry_period: 局部变量,指当前还可以尝试的时间周期,可以由rtpjitterbuffer中的rtx_retry_period参数获取。
当rtx_retry_period参数没有配置的时候,则根据latency_ns和当前的rtx_retry_timeout相减计算得出。
3) rtx_last: 定时器参数,记录最近一次重传请求的时间。主要用于update_timers。
4) rtx_base: 定时器参数,add_timer时候添加,为超时的基准时间。
5) rtx_retry: 定时器参数,记录下一次超时距离rtx_base的时间,是一个累加的参数,每次超时触发时候会增加rtx_retry_timeout。
6) rtx_delay: 定时器参数,由于重传而导致的额外的延迟,可以由参数rtx_delay配置,当rtx_delay参数没有设置时候,会根据avg_jitter和packet_spacing计算链路的延迟。
7) num_rtx_retry: 定时器参数,当前重试次数。
8) num_rtx_retries: rtpjitterbuffer参数,最大的重试次数。
/* the timeout for when we expected a packet expired */ static gboolean do_expected_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer, GstClockTime now) { GstRtpJitterBufferPrivate *priv = jitterbuffer->priv; GstEvent *event; guint delay, delay_ms, avg_rtx_rtt_ms; guint rtx_retry_timeout_ms, rtx_retry_period_ms; GstClockTime rtx_retry_period; GstClockTime rtx_retry_timeout; GstClock *clock; GST_DEBUG_OBJECT (jitterbuffer, "expected %d didn't arrive, now %" GST_TIME_FORMAT, timer->seqnum, GST_TIME_ARGS (now)); // 计算重传的时间间隔,主要由rtx_retry_timeout和rtx_min_retry_timeout计算 // 如果没有特殊配置,则会通过avg_rtx_rtt和avg_jitter来进行计算(自适应)。 rtx_retry_timeout = get_rtx_retry_timeout (priv); // 最大重传周期计算,由rtx_retry_period和当前得到的rtx_retry_timeout来计算。 rtx_retry_period = get_rtx_retry_period (priv, rtx_retry_timeout); GST_DEBUG_OBJECT (jitterbuffer, "timeout %" GST_TIME_FORMAT ", period %" GST_TIME_FORMAT, GST_TIME_ARGS (rtx_retry_timeout), GST_TIME_ARGS (rtx_retry_period)); delay = timer->rtx_delay + timer->rtx_retry; delay_ms = GST_TIME_AS_MSECONDS (delay); // 单位转换 rtx_retry_timeout_ms = GST_TIME_AS_MSECONDS (rtx_retry_timeout); rtx_retry_period_ms = GST_TIME_AS_MSECONDS (rtx_retry_period); avg_rtx_rtt_ms = GST_TIME_AS_MSECONDS (priv->avg_rtx_rtt); // 打包准备向上游发送的重传请求事件GstRTPRetransmissionRequest event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, gst_structure_new ("GstRTPRetransmissionRequest", "seqnum", G_TYPE_UINT, (guint) timer->seqnum, "running-time", G_TYPE_UINT64, timer->rtx_base, "delay", G_TYPE_UINT, delay_ms, "retry", G_TYPE_UINT, timer->num_rtx_retry, "frequency", G_TYPE_UINT, rtx_retry_timeout_ms, "period", G_TYPE_UINT, rtx_retry_period_ms, "deadline", G_TYPE_UINT, priv->latency_ms, "packet-spacing", G_TYPE_UINT64, priv->packet_spacing, "avg-rtt", G_TYPE_UINT, avg_rtx_rtt_ms, NULL)); // 统计数据更新,更新全局重传请求的统计数据和定时器自身的尝试次数 priv->num_rtx_requests++; timer->num_rtx_retry++; GST_OBJECT_LOCK (jitterbuffer); // 更新最近一次的重试时间。 if ((clock = GST_ELEMENT_CLOCK (jitterbuffer))) { timer->rtx_last = gst_clock_get_time (clock); timer->rtx_last -= GST_ELEMENT_CAST (jitterbuffer)->base_time; } else { timer->rtx_last = now; } GST_OBJECT_UNLOCK (jitterbuffer); /* calculate the timeout for the next retransmission attempt */ // 更新下一次超时时间。 timer->rtx_retry += rtx_retry_timeout; GST_DEBUG_OBJECT (jitterbuffer, "base %" GST_TIME_FORMAT ", delay %" GST_TIME_FORMAT ", retry %" GST_TIME_FORMAT ", num_retry %u", GST_TIME_ARGS (timer->rtx_base), GST_TIME_ARGS (timer->rtx_delay), GST_TIME_ARGS (timer->rtx_retry), timer->num_rtx_retry); // 重试次数超过rtpjitterbuffer参数rtx_max_retries // 或者重试的总时间(重传已经尝试的时间 + 重传的延迟) // 已经大于rtpjitterbuffer参数rtx_retry_period // 将这个定时器更新为LOST,下一次超时触发后会当成丢包处理。 if ((priv->rtx_max_retries != -1 && timer->num_rtx_retry >= priv->rtx_max_retries) || (timer->rtx_retry + timer->rtx_delay > rtx_retry_period)) { GST_DEBUG_OBJECT (jitterbuffer, "reschedule as LOST timer"); /* too many retransmission request, we now convert the timer * to a lost timer, leave the num_rtx_retry as it is for stats */ timer->type = TIMER_TYPE_LOST; timer->rtx_delay = 0; timer->rtx_retry = 0; } // 更新定时器信息。 reschedule_timer (jitterbuffer, timer, timer->seqnum, timer->rtx_base + timer->rtx_retry, timer->rtx_delay, FALSE); JBUF_UNLOCK (priv); // 向上游发送事件 gst_pad_push_event (priv->sinkpad, event); JBUF_LOCK (priv); return FALSE; }3.3 丢包处理
丢包事件触发后的代码逻辑比较简单,也没有涉及太多的变量。
/* a packet is lost */ static gboolean do_lost_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer, GstClockTime now) { GstRtpJitterBufferPrivate *priv = jitterbuffer->priv; GstClockTime duration, timestamp; guint seqnum, lost_packets, num_rtx_retry, next_in_seqnum; gboolean head; GstEvent *event; RTPJitterBufferItem *item; seqnum = timer->seqnum; timestamp = apply_offset (jitterbuffer, timer->timeout); duration = timer->duration; if (duration == GST_CLOCK_TIME_NONE && priv->packet_spacing > 0) duration = priv->packet_spacing; lost_packets = MAX (timer->num, 1); num_rtx_retry = timer->num_rtx_retry; /* we had a gap and thus we lost some packets. Create an event for this. */ if (lost_packets > 1) GST_DEBUG_OBJECT (jitterbuffer, "Packets #%d -> #%d lost", seqnum, seqnum + lost_packets - 1); else GST_DEBUG_OBJECT (jitterbuffer, "Packet #%d lost", seqnum); // 更新全局状态信息 priv->num_late += lost_packets; priv->num_rtx_failed += num_rtx_retry; // 更新下一个期望的包的序号 next_in_seqnum = (seqnum + lost_packets) & 0xffff; /* we now only accept seqnum bigger than this */ if (gst_rtp_buffer_compare_seqnum (priv->next_in_seqnum, next_in_seqnum) > 0) priv->next_in_seqnum = next_in_seqnum; // 打包丢包event,添加对应包序号的item至jbuf中,是否发送将由推送线程判断 // 主要功能是补齐包序号,在推送线程中会触发gap == 0的条件继续推送包。 /* create paket lost event */ event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, gst_structure_new ("GstRTPPacketLost", "seqnum", G_TYPE_UINT, (guint) seqnum, "timestamp", G_TYPE_UINT64, timestamp, "duration", G_TYPE_UINT64, duration, "retry", G_TYPE_UINT, num_rtx_retry, NULL)); item = alloc_item (event, ITEM_TYPE_LOST, -1, -1, seqnum, lost_packets, -1); rtp_jitter_buffer_insert (priv->jbuf, item, &head, NULL); // 删除定时器并唤醒推送线程。 /* remove timer now */ remove_timer (jitterbuffer, timer); if (head) JBUF_SIGNAL_EVENT (priv); return TRUE; }