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

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

被折叠的 条评论
为什么被折叠?



