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

1. 简介:

    本文主要描述gstreamer中rtpjitterbuffer的定时器线程的处理流程,定时器主要对丢包进行延迟处理。

2. 流程:

2.1 定时器线程主要流程:

    1) 当rtpjitterbuffer组件状态从READY升至PAUSED时,会创建出定时器的子线程。

    2) 从当前定时器中找到超时时间最早的定时器。

    3) 如果没有找到定时器,说明当前并没有定时器被添加,此时会挂起本线程,等待主线程上丢包后添加相应的定时器。

    4) 找到定时器后,与当前时间比较,如果已经超时,则处理超时事件,处理超时事件完成后回到4)。

    5) 如果这个定时器还没有超时,说明所有定时器都还没有到,本线程进入睡眠,待睡眠醒来时,回到2)。

流程如图:

gstreamer中rtpjitterbuffer的定时器线程及重传请求代码分析_第1张图片

2.2 超时重传处理流程:

    这里主要分析单个包丢失后的处理流程。

    1) 当一个不是预期的包到来,且序号大于预期包,则认为预期包可能丢包,这时候进入丢包处理流程。

    2) 首先通过函数calculate_expected计算第一次超时的时间,这里使用的是预期包和实际到达包的序号差和dts的比值,线性增大的时间戳作为超时时间,具体见下分析。

    3) 为对应包序添加定时器,此时非预期的包照常处理,依旧进入jbuf,但是由于包序不连续,推送线程不会把数据往下游发送。

    4) 定时器线程等待超时时间的到来。

    5) 如果超时到来之前,预期包到达,则删除定时器,进入正常的包处理流程,定时器线程唤醒后没有对应的定时器事件,则进行其他处理。

    6) 如果超时到来之前,包都没有到达,则定时器触发,需要判断当前重试次数是否已经达到上限,或者重试时间超过限制的总时间。

    7) 如果尝试满了,则将包设置为LOST,在下一个超时后执行do_lost_timeout操作,将该序号的包置位LOST的event向下游发送。

    8) 如果还可以继续尝试,则刷新当前尝试次数和尝试时间,重新调度定时器,再次加入调度。

流程如图:

gstreamer中rtpjitterbuffer的定时器线程及重传请求代码分析_第2张图片

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




你可能感兴趣的:(代码分析,RTP,gstreamer)