gstreamer中rtpjitterbuffer的定时器线程及重传请求代码分析

来源:互联网 发布:淘宝自定义装修教程 编辑:程序博客网 时间:2024/06/08 03:58

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 voidwait_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 voidcalculate_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 gbooleando_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 gbooleando_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;}




1 0
原创粉丝点击