Gstreamer Dash直播数据下载分析

文章详细阐述了GstreamerDash在处理直播数据下载时的关键任务,包括gst_adaptive_demux_stream_download_loop、gst_adaptive_demux_updates_loop和gst_system_clock_async_thread,以及如何处理音视频流的切换和播放列表更新。在DASH直播中,数据下载涉及流的结束判断、播放列表更新、错误处理和比特率适应等功能。

Gstreamer Dash直播数据下载分析

Gstreamer Dash直播数据下载分析始于列表下载,止于container数据送到文件demux,比如送到qtdemux,主要是数据下载,尔后的流程不在本文讨论范围。主要包括gst_adaptive_demux_stream_download_loop任务,gst_adaptive_demux_updates_loop任务和gst_system_clock_async_thread,_src_chain这几方面的任务,Dash直播的时候,音视频可能会分开,因此,数据下载可能会有多个任务,也就是gst_adaptive_demux_stream_download_loop可能会有多个,但分析一个不影响。

gst_adaptive_demux_updates_loop用于播放列表的更新,gst_adaptive_demux_stream_download_loop设置source,管理流下载,source用_src_chain函数向gst_adaptive_demux推流,_src_chain函数将流推给后端的demux。

播放过程中,同一个Adaptation Set中的Representation可以随意切换,并且复用同一条流,不用expose pad。如果dash或者hls要求切换pad,那么,当前流下载完成或者被取消以后,切换。因此需要特别注意当前流的下载状态。

详细分析如下:

gst_adaptive_demux_stream_download_loop任务

主要包含以下几个方面功能:

1. 流下载结束判断,结束任务。

2. PAD未Link时,待Link上了,重新下载数据。

3. 更新下一个下载片断的头部地址,数据地址,范围等信息。

4. 等待直播片断准备就绪

5. 数据下载

6. 直播数据更新fragment时遇到EOS出错,更新主列表,正常时更新当前流播放列表。这儿有一个问题,更新主列表时,exposed的旧流需要断开。

7. 非直播更新fragment时出错时,第一次出错时立即更新主列表。否则等待fragment时长一半,再更新主列表。超过一定次数后不会再重新下载。

8. 下一个片断更新和主播放列表的更新实作在hls/dash demux中进行。

/* this function will take the manifest_lock and will keep it until the end.
 * It will release it temporarily only when going to sleep.
 * Every time it takes the manifest_lock, it will check for cancelled condition
 */
static void
gst_adaptive_demux_stream_download_loop (GstAdaptiveDemuxStream * stream)
{
  GstAdaptiveDemux *demux = stream->demux;
  /* 下一次下载的时间,单位ns */
  GstClockTime next_download = gst_adaptive_demux_get_monotonic_time (demux);
……
  /* 流被取消,如下条件成立时此标记为TRUE.
   * 1:当前流是旧流,新流建立expose_streams时,remove掉当前流,释放demux->streams中的流。因为Gtask,这些流并没有完全被清理干净,接着放到demux->priv->old_streams中,在如下2的线程中继续清除。
   * 2:正常下载时,demux->priv->old_streams存在,下载线程还存在时。
   * 3:SEEK,FLUSH时,调用gst_adaptive_demux_stop_tasks时。
   */
  if (G_UNLIKELY (stream->cancelled)) {
    stream->last_ret = GST_FLOW_FLUSHING;
    g_mutex_unlock (&stream->fragment_download_lock);
    /* 直接返回了*/
    goto cancelled;
  }
  g_mutex_unlock (&stream->fragment_download_lock);
  /* Check if we're done with our segment,分段结束 */
  GST_ADAPTIVE_DEMUX_SEGMENT_LOCK (demux);
  if (demux->segment.rate > 0) {
    if (GST_CLOCK_TIME_IS_VALID (demux->segment.stop)
        && stream->segment.position >= stream->segment.stop) {
      GST_ADAPTIVE_DEMUX_SEGMENT_UNLOCK (demux);
      ret = GST_FLOW_EOS;
      gst_task_stop (stream->download_task);
      goto end_of_manifest;
    }
  } else {
    /* 后退播放时时,下载位置比start小,结束数据下载 */
    if (GST_CLOCK_TIME_IS_VALID (demux->segment.start)
        && stream->segment.position <= stream->segment.start) {
      GST_ADAPTIVE_DEMUX_SEGMENT_UNLOCK (demux);
      ret = GST_FLOW_EOS;
      gst_task_stop (stream->download_task);
      goto end_of_manifest;
    }
  }
  GST_ADAPTIVE_DEMUX_SEGMENT_UNLOCK (demux);
  /* Cleanup old streams if any,exposed pad时保存的旧流环境,它们的任务已经停止,做资源清理 */
  if (G_UNLIKELY (demux->priv->old_streams != NULL)) {
    GList *old_streams = demux->priv->old_streams;
    demux->priv->old_streams = NULL;
    GST_DEBUG_OBJECT (stream->pad, "Cleaning up old streams");
    g_list_free_full (old_streams,
        (GDestroyNotify) gst_adaptive_demux_stream_free);
    GST_DEBUG_OBJECT (stream->pad, "Cleaning up old streams (done)");
    /* gst_adaptive_demux_stream_free had temporarily released the manifest_lock.
     * Recheck the cancelled flag.
     */
    g_mutex_lock (&stream->fragment_download_lock);
    if (G_UNLIKELY (stream->cancelled)) {
      stream->last_ret = GST_FLOW_FLUSHING;
      g_mutex_unlock (&stream->fragment_download_lock);
      goto cancelled;
    }
    g_mutex_unlock (&stream->fragment_download_lock);
  }
  /* Restarting download, figure out new position
   * stream的pad没有Link上时为真,重新计算需要下载的位置并下载
   */
  if (G_UNLIKELY (stream->restart_download)) {
    GstEvent *seg_event;
    GstClockTime cur, ts = 0;
    gint64 pos;
    if (gst_pad_peer_query_position (stream->pad, GST_FORMAT_TIME, &pos)) {
      ts = (GstClockTime) pos;
    } else {
      /* query other pads as some faulty element in the pad's branch might
       * reject position queries. This should be better than using the
       * demux segment position that can be much ahead */
      GList *iter;
      for (iter = demux->streams; iter != NULL; iter = g_list_next (iter)) {
        GstAdaptiveDemuxStream *cur_stream =
            (GstAdaptiveDemuxStream *) iter->data;
        if (gst_pad_peer_query_position (cur_stream->pad, GST_FORMAT_TIME,
                &pos)) {
          ts = (GstClockTime) pos;
          break;
        }
      }
    }
    cur =
        gst_segment_to_stream_time (&stream->segment, GST_FORMAT_TIME,
        stream->segment.position);
    /* we might have already pushed this data */
    ts = MAX (ts, cur);
    if (GST_CLOCK_TIME_IS_VALID (ts)) {
      GstClockTime offset, period_start;
      offset =
          gst_adaptive_demux_stream_get_presentation_offset (demux, stream);
      period_start = gst_adaptive_demux_get_period_start_time (demux);
      /* TODO check return */
      gst_adaptive_demux_stream_seek (demux, stream, demux->segment.rate >= 0,
          0, ts, &ts);
      stream->segment.position = ts - period_start + offset;
    }
    /* The stream's segment is still correct except for
     * the position, so let's send a new one with the
     * updated position */
    seg_event = gst_event_new_segment (&stream->segment);
    gst_event_set_seqnum (seg_event, demux->priv->segment_seqnum);
    gst_pad_push_event (stream->pad, seg_event);
    stream->discont = TRUE;
    stream->restart_download = FALSE;
  }
  live = gst_adaptive_demux_is_live (demux);
  /* Get information about the fragment to download,让hls或者dash这类demux来更新,更新url,range 等参数,后面介绍这个函数 */
  ret = gst_adaptive_demux_stream_update_fragment_info (demux, stream);
  if (ret == GST_FLOW_OK) {
    /* wait for live fragments to be available,等待直播的fragment可用 */
    if (live) {
      gint64 wait_time =
          gst_adaptive_demux_stream_get_fragment_waiting_time (demux, stream);
      if (wait_time > 0) {
……
      }
    }
    stream->last_ret = GST_FLOW_OK;
    next_download = gst_adaptive_demux_get_monotonic_time (demux);
    ret = gst_adaptive_demux_stream_download_fragment (stream);
    if (ret == GST_FLOW_FLUSHING) {
      g_mutex_lock (&stream->fragment_download_lock);
      if (G_UNLIKELY (stream->cancelled)) {
        stream->last_ret = GST_FLOW_FLUSHING;
        g_mutex_unlock (&stream->fragment_download_lock);
        goto cancelled;
      }
      g_mutex_unlock (&stream->fragment_download_lock);
    }
  } else {
    stream->last_ret = ret;
  }
  switch (ret) {
    case GST_FLOW_OK:
      break;                    /* all is good, let's go */
    case GST_FLOW_EOS:
      /* we push the EOS after releasing the object lock ,直播*/
      if (gst_adaptive_demux_is_live (demux)
          && (demux->segment.rate == 1.0
              || gst_adaptive_demux_stream_in_live_seek_range (demux,
                  stream))) {
        GstAdaptiveDemuxClass *demux_class =
            GST_ADAPTIVE_DEMUX_GET_CLASS (demux);
        /* this might be a fragment download error, refresh the manifest, just in case,更新主列表,dash就是更新MPD文件,HLS会更新主M3U8文件 */
        if (!demux_class->requires_periodical_playlist_update (demux)) {
          ret = gst_adaptive_demux_update_manifest (demux);
          break;
          /* Wait only if we can ensure current manifest has been expired.
           * The meaning "we have next period" *WITH* EOS is that, current
           * period has been ended but we can continue to the next period,更新流的播放列表 */
        } else if (!gst_adaptive_demux_has_next_period (demux) &&
            gst_adaptive_demux_stream_wait_manifest_update (demux, stream)) {
          goto end;
        }
        /* 停止下载任务 */
        gst_task_stop (stream->download_task);
        if (stream->replaced) {
          goto end;
        }
      } else {
        gst_task_stop (stream->download_task);
      }
      /* demux中的所有流都结束EOS了 */
      if (gst_adaptive_demux_combine_flows (demux) == GST_FLOW_EOS) {
        if (gst_adaptive_demux_has_next_period (demux)) {
          /* 播放下一个period */
          gst_adaptive_demux_advance_period (demux);
          ret = GST_FLOW_OK;
        }
      }
      break;
    case GST_FLOW_NOT_LINKED: {
      GstFlowReturn ret;
      gst_task_stop (stream->download_task);
      /* demux的所有流都没有link */
      ret = gst_adaptive_demux_combine_flows (demux);
      if (ret == GST_FLOW_NOT_LINKED) {
        GST_ELEMENT_FLOW_ERROR (demux, ret);
      }
    }
      break;
    case GST_FLOW_FLUSHING:{
      GList *iter;
      for (iter = demux->streams; iter; iter = g_list_next (iter)) {
        GstAdaptiveDemuxStream *other;
        other = iter->data;
        /* 只是停止自身任务,并没有拉其他标志 */
        gst_task_stop (other->download_task);
      }
    }
      break;
    default:
      if (ret <= GST_FLOW_ERROR) {
        gboolean is_live = gst_adaptive_demux
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值