从零开始成为GStreamer专家——MPEG-DASH HLS开发

MPEG-DASH开发

        DASH(Dynamic Adaptive Streaming over HTTP),在GStreamer中,DASH,HLS,MSS的adaptive_demux_sink_event收到GST_EVENT_EOS之后,才会调用  demux_class->process_manifest (demux, manifest_buffer))处理播放列表。播放列表组织如下:        

        

         MPD文件由一个或多个Period构成,简单来讲,period就是周期,就是时间轴上的一段时间,如下图所示:

        mimeType是互联网媒体类型(Internet media type)的一种别称。标准的MIME结构是“类型名/子类型名”,目前已被注册的类型名有application、audio、example、image、message、model、multipart、text,以及video,如audio/mp4表示MP4音频文件,video/mp4表示MP4视频文件。
        AdaptationSet包含一个或者多个音视频媒体的MIME TYPE类型描述,通常包含若干个representations,一个period由一个或者多个AdaptationSet构成。DASH对段定义了三种表示方式,Representation由如下这几个选项之中的一个组成:
        (1) 段列表表示:由SegmentList元素列表组成;

<?xml version="1.0"?>
<!-- MPD file Generated with GPAC version 0.5.2-DEV-rev322-gf6fed6c-master  at 2015-05-11T10:14:04.782Z-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" minBufferTime="PT1.500S" type="static" mediaPresentationDuration="PT0H22M36.255S" maxSegmentDuration="PT0H0M10.077S" profiles="urn:mpeg:dash:profile:mp2t-main:2011">
 <ProgramInformation moreInformationURL="segment/">
  <Title>547_dash.mpd generated by GPAC</Title>
 </ProgramInformation>
 <BaseURL>segment/</BaseURL>

 <Period duration="PT0H22M36.255S">
  <AdaptationSet segmentAlignment="true" maxWidth="1920" maxHeight="1080" par="16:9">
   <ContentComponent id="256" contentType="video" />
   <ContentComponent id="257" contentType="audio" />
   <Representation id="1" mimeType="video/mp2t" width="1920" height="1080" audioSamplingRate="44100" startWithSAP="1" bandwidth="6672839">
    <SegmentList timescale="90000" duration="900000" presentationTimeOffset="3003">
     <RepresentationIndex sourceURL="segment.six"/>
     <SegmentURL media="segment1.ts"/>
     <SegmentURL media="segment2.ts"/>
     <SegmentURL media="segment3.ts"/>
     <SegmentURL media="segment4.ts"/>
……
     <SegmentURL media="segment136.ts"/>
    </SegmentList>
   </Representation>
  </AdaptationSet>
 </Period>
</MPD>

(2) 段模板表示:一个SegmentTemplate;

<?xml version="1.0" encoding="utf-8"?>
<!-- MPD file Generated with GPAC version 0.5.1-DEV-rev4736M  on 2013-09-12T09:20:37Z-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" minBufferTime="PT1.500000S" type="static" mediaPresentationDuration="PT0H9M54.00S" availabilityStartTime="2012-09-05T09:00:00Z" profiles="urn:mpeg:dash:profile:isoff-live:2011,http://dashif.org/guidelines/dash264">
  <Period id="" duration="PT0H9M54.00S">
    <AdaptationSet segmentAlignment="true" maxWidth="1280" maxHeight="720" maxFrameRate="24" par="16:9">
      <Representation id="1" mimeType="video/mp4" codecs="avc1.42c01f" width="512" height="288" frameRate="24" sar="1:1" startWithSAP="1" bandwidth="1162074">
        <SegmentTemplate timescale="12288" media="BBB_512_640K_video_$Time$.mp4" initialization="BBB_512_640K_video_init.mp4">
          <SegmentTimeline>
            <S d="61440" r="-1" />
          </SegmentTimeline>
        </SegmentTemplate>
      </Representation>
      <Representation id="2" mimeType="video/mp4" codecs="avc1.42c01f" width="768" height="432" frameRate="24" sar="1:1" startWithSAP="1" bandwidth="1801786">
        <SegmentTemplate timescale="12288" media="BBB_768_1440K_video_$Time$.mp4" initialization="BBB_768_1440K_video_init.mp4">
          <SegmentTimeline>
            <S d="61440" r="-1" />
          </SegmentTimeline>
        </SegmentTemplate>
      </Representation>
      <Representation id="3" mimeType="video/mp4" codecs="avc1.42c01f" width="1280" height="720" frameRate="24" sar="1:1" startWithSAP="1" bandwidth="4487961">
        <SegmentTemplate timescale="12288" media="BBB_1280_4M_video_$Time$.mp4" initialization="BBB_1280_4M_video_init.mp4">
          <SegmentTimeline>
            <S d="61440" r="-1" />
          </SegmentTimeline>
        </SegmentTemplate>
      </Representation>
    </AdaptationSet>
    <AdaptationSet segmentAlignment="true">
      <Representation id="4" mimeType="audio/mp4" codecs="mp4a.40.29" audioSamplingRate="48000" startWithSAP="1" bandwidth="33028">
        <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2" />
        <SegmentTemplate timescale="48000" media="BBB_32k_$Time$.mp4" initialization="BBB_32k_init.mp4">
          <SegmentTimeline>
            <S d="239616" r="-1" />
          </SegmentTimeline>
        </SegmentTemplate>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>

    段模板可以用动态变化的参数来描述段列表,不用像SegmentList那样将所有的文件列出来,简化MPD文件长度。
(3) 单段表示,由一个BaseURL元素,一个SegmentBase构成。不能有SegmentTemplate、SegmentList元素。  

<Period>
    <!-- Audio -->
    <AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1">
      <Representation audioSamplingRate="44100" bandwidth="141962" codecs="mp4a.40.2" id="audio-und">
        <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
        <BaseURL>media-audio-und.mp4</BaseURL>
        <SegmentBase indexRange="628-3599"> /* 指出段的索引信息,即sidx在上述URL代表的文件中的字节范围 */
          <Initialization range="0-627"/> /* 指出初始化信息在上述URL代表的文件中的字节范围 */
        </SegmentBase>
      </Representation>
    </AdaptationSet>
    <!-- Video -->
    <AdaptationSet maxHeight="534" maxWidth="1280" mimeType="video/mp4" minHeight="214" minWidth="512" segmentAlignment="true" startWithSAP="1">
      <Representation bandwidth="2850840" codecs="avc1.42C01F" frameRate="24" height="534" id="video-6" scanType="progressive" width="1280">
        <BaseURL>media-video-6.mp4</BaseURL>
        <SegmentBase indexRange="693-3664">
          <Initialization range="0-692"/>
        </SegmentBase>
      </Representation>
    </AdaptationSet>
  </Period>

        Segments构成Representation,每个Representation中的内容按照一定的规则被切分成一段段的的Segments;这些Segments可能共用一个URL,由Byte range指定范围或者使用不同的URL。同时,Segments还可以细分成SubSegments,SubSegments表示Segments中更细的Access unit。SubSegment index表示Subsegments的presentation time range和byte position,用户可以先获取index,再通过HTTP的byte range requests去请求相应的Subsegment。
        AdaptationSet中的码流内容一样,区别在于比特率,分辨率,声道数等等,不同的Representation用来描述这些差异,实际上的流媒体文件则存在于Segments段描述中。
        直播情况下,需要周期地去服务器更新MPD文件,服务器移除旧的Period,添加新的Period。  更新的周期由minimumUpdatePeriod字段指定,当 type = static 时,minimumUpdatePeriod属性不应该出现。

有这样一些注意事项:

1. 列表中可能存在多种内容一样,但编码格式等等不同的URL地址

 2.DASH需要GstDashDemux->video demux/audio demux的连接,也就是说dash后面要接多个demux,有多少条流,就有多少个demux,并且demux前面还最好有队列,开销也是很大的。下面这段代码用于将每条流的pad外接。dash本身还是具有多个queue的demux。

  for (iter = demux->streams; iter; iter = g_list_next (iter)) {
    GstAdaptiveDemuxStream *stream = iter->data;

    if (!gst_adaptive_demux_expose_stream (demux,
            GST_ADAPTIVE_DEMUX_STREAM_CAST (stream))) {
      /* TODO act on error */
    }
  }

        HLS稍有不同,HLS是GstHlsDemux之后,接一个比如TS/MP4 demux,TS/MP4 demux再分离出来A/V。

        如上所述GstHlsDemux和GstDashDemux之后,不同分辨率的码流可以切换,轨道可以切换。一般说来,同一影片时间基数相同,切换方便,不同影片的流间切换需要技巧,也需要转换。 GstHlsDemux,GstDashDemux输出有多个,demux后的输出流也会是多条,这是一种多对多的关系。上报给客户会提到有多少种码流,这些码流的分辨率是多少。只有当前播放的流才能获取具体的轨道信息,其他的码流Pad由于没有创建demux处于Mute状态,作如下设计:

        非直播情况下,第一次处理的period就是编号为0的period,直播时,更新MPD表时处理的period是初时获取的。GStreamer处理period下的所有AdaptationSet时创建流。
1. 取得一个AdaptationSet,创建GstActiveStream类型流stream并且初始化。设置stream的URL起始为0,cur_adapt_set为当前的set。
2. 找到最小带宽bandwidth的representation
3. 获取MIME TYPE,GST_STREAM_AUDIO,GST_STREAM_VIDEO等等,并将stream加入到active_streams链表中。
4. 为active_streams链表中每一条stream创建非ghost的srcpad, caps和GstDashDemuxStream对像,每一个AdaptationSet都有用gst_adaptive_demux_stream_new创建一个GstDashDemuxStream对象,它的pad将会是上述src pad。

GstAdaptiveDemuxStream *stream = g_malloc0 (demux->stream_struct_size);
stream->pad = pad;
stream->demux = demux;
stream->do_block = TRUE;
demux->priv->preroll_pending++;

将流对象将会被划设置成lock态,并且preroll_pending。
5. 创建下载线程gst_adaptive_demux_stream_download_loop,并将GstAdaptiveDemuxStream放到GstAdaptiveDemux也就是GstDashDemux的parent的next_streams链表中。
6. SEEK到即将要开始播放的位置。
也就是说,每一个AdaptationSet中都选择出了带宽最小的流加入到active_streams链表中并完成了准备。
下面看一下如何将src pad报告出去。
1. gst_adaptive_demux_prepare_streams配置prepared_streams, demux->prepared_streams = demux->next_streams;
2. 为每条流准备事件,设置成lock态,preroll处理完所有lock态的stream之后,preroll_pending为0时,将pad 开放出去。

static gboolean
gst_adaptive_demux_expose_stream (GstAdaptiveDemux * demux,
    GstAdaptiveDemuxStream * stream)
{
  gboolean ret;
  GstPad *pad = stream->pad;
  GstCaps *caps;

  if (stream->pending_caps) {
    gst_pad_set_caps (pad, stream->pending_caps);
……
  } else {
    caps = gst_pad_get_current_caps (pad);
  }
……
  /* Don't hold the manifest lock while exposing a pad */
  GST_MANIFEST_UNLOCK (demux);
  ret = gst_element_add_pad (GST_ELEMENT_CAST (demux), pad);
  GST_MANIFEST_LOCK (demux);

  return ret;
}

pad是手动添加的,但是元素element的pad移除可能是自动的,体现在下面几个时间:

1. 元素状态切换时,由PAUSE->READY时,移除持有的PAD。

2.元素所在BIN状态切换由PAUSE->READY时,移除子元素并且移除其PAD。

DASH的profiles

Profiles
IdentifierReferenceSectionComment
urn:mpeg:dash:profile:full:2011ISO/IEC 23009-1section 8.2identifier for MPEG-DASH Full profile
urn:mpeg:dash:profile:isoff-on-demand:2011ISO/IEC 23009-1section 8.3MPEG-DASH ISO Base media file format On Demand profile
urn:mpeg:dash:profile:isoff-live:2011ISO/IEC 23009-1section 8.4identifier for MPEG-DASH ISO Base media file format live profile.
urn:mpeg:dash:profile:isoff-main:2011ISO/IEC 23009-1section 8.5identifier for MPEG-DASH ISO Base media file format main profile.
urn:mpeg:dash:profile:mp2t-main:2011ISO/IEC 23009-1section 8.6identifier for MPEG-DASH MPEG-2 TS main profile.
urn:mpeg:dash:profile:mp2t-simple:2011ISO/IEC 23009-1section 8.7identifier for MPEG-DASH MPEG-2 TS simple profile.
urn:3GPP:PSS:profile:DASH103GPP TS26.247section 7.3.3identifier for 3GP-DASH Release-10 profile.
urn:dvb:dash:profile:dvb-dash:2014HbbTV 2.0.1HbbTV 2.0HbbTV 2.0 DASH profiles.
urn:hbbtv:dash:profile:isoff-live:2012HbbTV 1.5HbbTV 1.5HbbTV 1.5 DASH profiles

ISO BMFF On Demand——ISO BMFF点播

        此profile能产生最佳的点播配置,以及带来播放器和服务器之间的最佳兼容性,有如下要求:

  • A single segment for each representation (one audio or video track per file).A/V独立,每个场景单独分断。
  • Subsegments are aligned across representations (GOP aligned fragments).相同场景的不同子片断以GOP方式对齐。
  • Subsegments must begin with a Stream Access Point (IDR/keyframe).子片断以SAP开始。
  • The segment is indexed using the Segment Index ('sidx').

ISO BMFF Main

所有的 DASH players 需要支持此 profile.

ISO BMFF Live

        上面描述的 On Demand 和 Main 配置文件都需要在字节范围内请求 HTTP。 对于不支持字节范围的播放器或 CDNs,可以生成基于索引的片段文件以及使用 SegmentTemplate 的 MPD,如 DASH Live 。

HLS

HLS的数据列表更新和数据下载配合

1. gst_adaptive_demux_updates_loop更新播放列表
2. 某个片断下载EOS之后,调用如下流程推进m3u8列表的current,m3u8->current_file = m3u8->current_file->next;

_src_event->gst_adaptive_demux_eos_handling->gst_adaptive_demux_stream_advance_fragment->gst_adaptive_demux_stream_advance_fragment_unlocked->gst_hls_demux_advance_fragment->gst_m3u8_advance_fragment 

3. 数据下载线程数据下载完成之后,用如下流程获取新的fragment的信息

gst_adaptive_demux_stream_download_loop->gst_adaptive_demux_stream_update_fragment_info->gst_hls_demux_update_fragment_info->gst_m3u8_get_next_fragment

就这样,current信息更新到stream->fragment中。
4. 如果没有下一个切片的信息,current会被置空,返回GST_FLOW_EOS错误信息,同时This playlist doesn't contain more fragments信息会打印出来。此时下载线程会在gst_adaptive_demux_stream_wait_manifest_update函数中一直等待列表更新,直到退出或者列表已经更新。
从hls的角度,GstM3U8中m3u8->sequence就是当前播放文件的序号,初始值为-1

加密DRM的一些知识:

        DASH,HLS在加密流领域应用广泛:

        AES(Advanced Encryption Standard)是最流行的加密技术之一。
        MSS(Microsoft Smooth Streaming)
        CENC(Common Encryption specification),视频既用cenc(AES-128 CTR),也可以用cbcs(AES-128 CBC)加密。CTR代表计数器模式;CBC代表密文分组链接模式。CENC意味着内容提供商仅需加密视频一次,并且任何解密模块都可以解密它。

        主要的DRM技术:Apple FairPlay、Google Widevine和Microsoft PlayReady。
        在DRM中,密钥ID可以是公开的,与DASH或者HLS清单一起被发送到视频播放器,也可以在视频码流中插入一些包含独特信息的字节来描述此ID。
        加密密钥则存储在和DRM许可证服务器一起工作的KMS(密钥库)中。当客户端需要播放加密视频时,通过密钥ID向DRM许可证服务器请求解密密钥。如果DRM许可证服务器对请求(认证请求)认可,它将要求密钥库提供与该密钥ID对应的解密密钥。

CENC加密有两个标准文档
ISO/IEC 23001-7 : Common encryption in ISO base media file format files
ISO/IEC 23001-9 :Common encryption of MPEG-2 transport streams

DRM Decryptor sink caps枚举:

 application/x-webm-enc, original-media-type=(string)video/webm;
 application/x-cenc, original-media-type=(string)video/webm, protection-system=(string)1077efec-c0b2-4d02-ace3-3c1e52e2fb4b;
 application/x-cenc, original-media-type=(string)video/webm, protection-system=(string)edef8ba9-79d6-4ace-a3c8-27dcd51d21ed;
 application/x-cenc, original-media-type=(string)video/webm, protection-system=(string)9a04f079-9840-4286-ab92-e65be0885f95;

 application/x-webm-enc, original-media-type=(string)video/mp4;
 application/x-cenc, original-media-type=(string)video/mp4, protection-system=(string)1077efec-c0b2-4d02-ace3-3c1e52e2fb4b; 
 application/x-cenc, original-media-type=(string)video/mp4, protection-system=(string)edef8ba9-79d6-4ace-a3c8-27dcd51d21ed;
 application/x-cenc, original-media-type=(string)video/mp4, protection-system=(string)9a04f079-9840-4286-ab92-e65be0885f95; 
 
 application/x-webm-enc, original-media-type=(string)video/x-h264;
 application/x-cenc, original-media-type=(string)video/x-h264, protection-system=(string)1077efec-c0b2-4d02-ace3-3c1e52e2fb4b; 
 application/x-cenc, original-media-type=(string)video/x-h264, protection-system=(string)edef8ba9-79d6-4ace-a3c8-27dcd51d21ed;
 application/x-cenc, original-media-type=(string)video/x-h264, protection-system=(string)9a04f079-9840-4286-ab92-e65be0885f95;
 
 application/x-webm-enc, original-media-type=(string)video/x-h265; 
 application/x-cenc, original-media-type=(string)video/x-h265, protection-system=(string)1077efec-c0b2-4d02-ace3-3c1e52e2fb4b;
 application/x-cenc, original-media-type=(string)video/x-h265, protection-system=(string)edef8ba9-79d6-4ace-a3c8-27dcd51d21ed;
 application/x-cenc, original-media-type=(string)video/x-h265, protection-system=(string)9a04f079-9840-4286-ab92-e65be0885f95; 
 
 application/x-webm-enc, original-media-type=(string)video/x-vp9;
 application/x-cenc, original-media-type=(string)video/x-vp9, protection-system=(string)1077efec-c0b2-4d02-ace3-3c1e52e2fb4b;
 application/x-cenc, original-media-type=(string)video/x-vp9, protection-system=(string)edef8ba9-79d6-4ace-a3c8-27dcd51d21ed; 
 application/x-cenc, original-media-type=(string)video/x-vp9, protection-system=(string)9a04f079-9840-4286-ab92-e65be0885f95; 
 
 application/x-webm-enc, original-media-type=(string)audio/mp4;
 application/x-cenc, original-media-type=(string)audio/mp4, protection-system=(string)1077efec-c0b2-4d02-ace3-3c1e52e2fb4b; 
 application/x-cenc, original-media-type=(string)audio/mp4, protection-system=(string)edef8ba9-79d6-4ace-a3c8-27dcd51d21ed;
 application/x-cenc, original-media-type=(string)audio/mp4, protection-system=(string)9a04f079-9840-4286-ab92-e65be0885f95; 
 
 application/x-webm-enc, original-media-type=(string)audio/opus;
 application/x-cenc, original-media-type=(string)audio/opus, protection-system=(string)1077efec-c0b2-4d02-ace3-3c1e52e2fb4b; 
 application/x-cenc, original-media-type=(string)audio/opus, protection-system=(string)edef8ba9-79d6-4ace-a3c8-27dcd51d21ed; 
 application/x-cenc, original-media-type=(string)audio/opus, protection-system=(string)9a04f079-9840-4286-ab92-e65be0885f95; 
 
 application/x-webm-enc, original-media-type=(string)audio/mpeg; 
 application/x-cenc, original-media-type=(string)audio/mpeg, protection-system=(string)1077efec-c0b2-4d02-ace3-3c1e52e2fb4b;
 application/x-cenc, original-media-type=(string)audio/mpeg, protection-system=(string)edef8ba9-79d6-4ace-a3c8-27dcd51d21ed;
 application/x-cenc, original-media-type=(string)audio/mpeg, protection-system=(string)9a04f079-9840-4286-ab92-e65be0885f95; 
 
 application/x-webm-enc, original-media-type=(string)audio/webm;
 application/x-cenc, original-media-type=(string)audio/webm, protection-system=(string)1077efec-c0b2-4d02-ace3-3c1e52e2fb4b;
 application/x-cenc, original-media-type=(string)audio/webm, protection-system=(string)edef8ba9-79d6-4ace-a3c8-27dcd51d21ed;
 application/x-cenc, original-media-type=(string)audio/webm, protection-system=(string)9a04f079-9840-4286-ab92-e65be0885f95

UUID

保存媒体格式,16字节UUID之后紧跟真正的数据。

UUIDFormatReference
0537cdab-9d0c-4431-a72a-fa561f2a113eExifExifTool
2c4c0100-8504-40b9-a03e-562148d6dfebPhotoshop Image ResourcesExifTool
33c7a4d2-b81d-4723-a0ba-f1a3e097ad38IPTC-IIM
8974dbce-7be7-4c51-84f9-7148f9882554PIFF Track Encryption BoxPIFF specification
96a9f1f1-dc98-402d-a7ae-d68e34451809GeoJP2 World File BoxGeoJP2 specification
a2394f52-5a9b-4f14-a244-6c427c648df4PIFF Sample Encryption BoxPIFF specification
b14bf8bd-083d-4b43-a5ae-8cd7d5a6ce03GeoJP2 GeoTIFF BoxGeoJP2 specification
be7acfcb-97a9-42e8-9c71-999491e3afacXMPXMP specification
d08a4f18-10f3-4a82-b6c8-32d8aba183d3PIFF Protection System Specific Header BoxPIFF specification

MP4中IV数据获取方式:

        gstreamer的qtdemux没有显式处理senc结构,FFMPEG的mov.c有显示处理,但gstreamer在qtdemux_parse_moof函数中,当读到saiz之后,用其中的偏移信息,直接跳到了senc的内容部分,也是解码了senc中的iv的。由于dash传输的mp4有多个moof,所以,这些moof有可能有的是不加密的。 

HLS加密方式选择

        gstreamer hls-crypto类型是auto,包含'nettle', 'libgcrypt', 'openssl'三类模块。

        Nettle是一个底层的加密库,密码库,是低级密码函数集合,是一个GNU软件包,GnuTLS使用Nettle。GnuTLS 实现了SSL,TLS和DTLS协议的安全通信。
        Nettle通过低级加密并为其提供一个简单而通用的接口。特别是,它不做算法选择、内存分配或任何I/O。因此,Nettle 旨在提供一个核心密码库,在该库上可以构建许多应用程序和上下文特定的接口。然后可以共享这些接口的代码、测试用例、基准测试、文档等,而无需复制Nettle的密码代码。

        libgcrypt是一个非常成熟的加密算法库,也是著名的开源加密软件GnuPG的底层库,支持多种对称、非对称加密算法,以及多种Hash算法。Libgcrypt 依赖于库`libgpg-error’,是一个提供加密构建块的库。Libgcrypt库的所有接口(数据类型和函数)都在头文件gcrypt.h中定义。必须直接或通过其他一些头文件将其包含在使用库的所有源文件中。

        OpenSSL是一个强大的,商用的,功能齐全的,开放源代码的加密库,应用程序可以使用这个包来进行安全通信。 整个软件包大概可以分成三个主要的功能部分:SSL协议库、应用程序以及密码算法库。采用C语言作为开发语言,OpenSSL支持Linux、Windows、BSD、Mac、VMS等平台,作为一个基于密码学的安全开发包,OpenSSL提供的功能相当强大和全面,囊括了主要的密码算法、常用的密钥和证书封装管理功能以及SSL协议,并提供了丰富的应用程序供测试或其它目的使用。

        Gstreamer需要SSL/TLS协议,因此建议选择用OpenSSL。

        Playready中tenc字段描述的default_KID,是一个 16 字节的大尾部数组,上面定义为存储在 Common Encryption ContentProtection 描述符元素中的 cenc:default_KID 属性中作为 UUID 字符串,但是,mspr:kid 中的 KID 值是一个 base64 编码的小 endian GUID 解释,用于定义用于存储大型 endian UUID 的“tenc”default_KID字节数组。详见使用 Microsoft PlayReady DASH 内容保护 - PlayReady | Microsoft Learn

其他

1. 下载流程:

gst_adaptive_demux_stream_download_loop
        调用demux_stream_update_fragment_info获取下一次要下载的uri的信息
                调用gst_adaptive_demux_stream_download_fragment 下载直到下载完成

2. GST_BIN_FLAG_STREAMS_AWARE是说当前bin能够处理其他元素的添加,删除pad操作,而不必等到元素发送no-more-pads信号。

3. SegmentTimeline:

        SegmentTimeline 描述每个区段可用于播放的时间。每个 SegmentTemplate 会有一个 SegmentTimeline。S:S 描述可以取得区段的时间 (t 值)、区段的持续时间 (d 值),以及多少额外的连续区段具有此相同持续时间 (r 值) 的计数,也就是重复次数。 SegmentTimeline 中有一或多个区段。

<SegmentTimeline>
……
<S d="75333300" />
<S d="83333300" r="1" />
<S d="98833300" />
<S d="89166700" />
<S d="83333300" r="1" />
……
<S d="87666700" />
<S d="83333300" r="13" />
</SegmentTimeline>

4.DASH中presentationTimeOffset的作用:
        EPT:是分段最早的呈现时间,Earliest Presentation Time,指一个period中,各个分段segment相对于period的最早显示时间,相对时间较容易查找buffer时间BufferPosition。
4. anchor time:基准时间
        DASH有共享时间线,用来同步各条流,流中Segments自身也包含时间信息用于同步和无缝切换。如果period第一个Segment的EPT错误地设置为非0,可以通过presentationTimeOffset来纠正。

--- Period 1 ---
MSE.timestampOffset = Period@start = 0
BufferPosition(Seg 1) = MSE.timestampOffset + EPT = 0 + 0 = 0 
BufferPosition(Seg 4) = MSE.timestampOffset + EPT = 0 + 6 = 6

--- Period 2 ---
MSE.timestampOffset = Period@start = 8
BufferPosition(Seg 1) = MSE.timestampOffset + EPT = 8 + 0 = 8
BufferPosition(Seg 2) = MSE.timestampOffset + EPT = 8 + 2 = 10
/* 每个分段显示时间2秒,Seg5本来应该从第12秒开始,但此时EPT误设置不为0,导致时间错误 */
--- Period 3 ---
MSE.timestampOffset = Period@start = 12
BufferPosition(Seg 5) = MSE.timestampOffset + EPT = 12 + 8 = 20 

/* 通过presentationTimeOffset来校正时间 */
--- Period 3 ---
MSE.timestampOffset = Period@start - @presentationTimeOffset = 12 - 8 = 4
BufferPosition(Seg 5) = MSE.timestampOffset + EPT = 4 + 8 = 12
BufferPosition(Seg 6) = MSE.timestampOffset + EPT = 4 + 10 = 14 

参考:https://websites.fraunhofer.de/video-dev/common-pitfalls-in-mpeg-dash-streaming/

adaptivedemux2

新的dash/hls分析插件,位于gst-plugins-good\ext\adaptivedemux2,新的adaptivedemux2暴露出来的pad其实不是demux出来的source stream。而旧的通过gst_adaptive_demux_expose_streams是实实在在输出的属于demux过后的属于每一个URL的source stream,是数据来源。
新的adaptivedemux2的流stream也是在gst_dash_demux_setup_all_streams这样的函数中创建的,与之前不同的是,没有创建exposed pad,但是它创建了tracks,新的adaptivedemux2是在gst_adaptive_demux_output_slot_new用audio和video的模板创建的几个输出pad。这个pad属于output_period->tracks,tracks是在stream setup的时候创建的。track中的数据是这样来的:

track_queue_data_locked                     gst-plugins-good\ext\adaptivedemux2\gstadaptivedemux-track.c(414)
_track_sink_chain_function                  gst-plugins-good\ext\adaptivedemux2\gstadaptivedemux-track.c(564)
……pad传输,这个parse是用来解析DEMUX出来的ES流的。
gst_base_parse_push_frame                   gstreamer\libs\gst\base\gstbaseparse.c(2595)
gst_base_parse_chain                        gstreamer\libs\gst\base\gstbaseparse.c(3219)
……pad传输
gst_qtdemux_push_buffer                     gst-plugins-good\gst\isomp4\qtdemux.c(5941)
gst_qtdemux_split_and_push_buffer           gst-plugins-good\gst\isomp4\qtdemux.c(6070)
gst_qtdemux_decorate_and_push_buffer        gst-plugins-good\gst\isomp4\qtdemux.c(6190)
gst_qtdemux_process_adapter                 gst-plugins-good\gst\isomp4\qtdemux.c(7630)
……pad传输,经过了parsebin,typefind,qtdemux的pad
gst_adaptive_demux2_stream_push_buffer      gst-plugins-good\ext\adaptivedemux2\gstadaptivedemux-stream.c(714)
gst_dash_demux_stream_handle_isobmff        gst-plugins-good\ext\adaptivedemux2\dash\gstdashdemux.c(3539)
gst_dash_demux_stream_data_received         gst-plugins-good\ext\adaptivedemux2\dash\gstdashdemux.c(3663)
gst_adaptive_demux2_stream_parse_buffer     gst-plugins-good\ext\adaptivedemux2\gstadaptivedemux-stream.c(762)
on_download_progress                        gst-plugins-good\ext\adaptivedemux2\gstadaptivedemux-stream.c(1332)
transfer_report_progress_cb                 gst-plugins-good\ext\adaptivedemux2\downloadhelper.c(132)
……
_gst_adaptive_demux_loop_thread             gst-plugins-good\ext\adaptivedemux2\gstadaptivedemuxutils.c(205)

所有一个stream可能会有多个track。对于我们期望尽可能少地依赖gstreamer的库而言,gst_adaptive_demux2_stream_push_buffer之后的parsebin就不需要了,可以用其他替换,track也不需要了。所以新的adaptivedemux2其实是把流解析成了ES流,缺点是这会对gstreamer原生播放器playbin产生依赖,就是依赖parsebin。

adaptivedemux2关于DASH MP4 SEEK流程

有关的结构体和解释如下:

struct _GstDashDemux2Stream
{
  GstAdaptiveDemux2Stream parent;

  gint index; /*数据流在MPD文件中的索引*/
  GstActiveStream *active_stream;

  /* Track provided by this stream */
  GstAdaptiveDemuxTrack *track; /* 数据流对应的轨道信息 */

  GstMediaFragmentInfo current_fragment; /* 当前片段的媒体信息 */

  /* index parsing */
  GstSidxParser sidx_parser; /* SIDX(Segment Index)解析器 */
  GstClockTime sidx_position; /* SIDX的位置 */
  gint64 sidx_base_offset; /* 第一个索引表项在码流中的位置 */
  gboolean allow_sidx;
  GstClockTime pending_seek_ts; /* 待处理的seek时间戳 */

  GstAdapter *adapter; /* 适配器,用于将数据流写入到缓冲区中。*/
  /* current offset of the first byte in the adapter / last byte we pushed or
   * dropped*/
  guint64 current_offset; /* 当前的数据类型在适配器中的首字节在流中的位置。 */
  /* index = 1, header = 2, data = 3 */
  guint current_index_header_or_data; /* 当前解析的原始数据类型。 */

  /* ISOBMFF box parsing */
  gboolean is_isobmff; /* 是否为ISOBMFF(ISO Base Media File Format)格式 */
  struct {
    /* index = 1, header = 2, data = 3 */
    guint32 current_fourcc; /* 当前正在解析的ISOBMFF box的FourCC(Four Character Code)类型 */
    guint64 current_start_offset; /* 当前解析的ISOBMFF box在数据流中的起始偏移量。*/
    guint64 current_size; /* 当前解析的ISOBMFF box的大小 */
  } isobmff_parser;

  GstMoofBox *moof; /* 当前MOOF(Movie Fragment)信息 */
  guint64 moof_offset, moof_size; /* MOOF的偏移量和大小 */
  GArray *moof_sync_samples; /* 码流中用于同步的那些关键帧样本点,这些样本点有关键帧的索引、时间戳、偏移量等信息,这个关键帧可能不是真正的关键帧,这儿采用了一种说法,叫"非非同步叫同步帧",用于KEY UNIT tric时,SEEK不用关注。moof也是用于这个目的 * /
  guint current_sync_sample; /* 当前使用的那个关键帧样本的索引*/

  guint64 moof_average_size; /* MOOF的平均大小 */
  guint64 keyframe_average_size; /* 关键帧的平均大小 */
  guint64 keyframe_average_distance; /*  关键帧的平均间隔 */
  gboolean first_sync_sample_after_moof, first_sync_sample_always_after_moof; /* 是否为MOOF后的第一个同步样本,就是ES流, 是否第一个同步采样总是在MOOF后 */

  /* Internal position value, at the keyframe/entry level */
  GstClockTime actual_position; /* 当前位置的时间戳 */
  /* Timestamp of the beginning of the current fragment */
  GstClockTime current_fragment_timestamp; /* 当前片段的时间戳 */
  GstClockTime current_fragment_duration; /* 当前片段的持续时间 */
  GstClockTime current_fragment_keyframe_distance; /* 当前片段中关键帧的间隔 */

  /* Average keyframe download time (only in trickmode-key-units) */
  GstClockTime average_download_time; /* 平均下载时间 */
  /* Cached target time (only in trickmode-key-units) */
  GstClockTime target_time;
  /* Average skip-ahead time (only in trickmode-key-units) */
  GstClockTime average_skip_size;

  gchar *last_representation_id;
};

typedef struct _GstSidxBoxEntry
{
  /* 索引对应的流编号。如果这个索引不是第一级索引,存在上一级索引的话,这个ID和父索引的ID相同,一般说来,索引是对Segment 而言的,文件如果有多个Segment 就可以存在一个父索引,多个子索引。*/
  gboolean ref_type; 
  /* 当前索引表项指向的媒体数据的第一个字节到下一个索引表项指向的第一个字节的数据长度。*/
  guint32 size;
  /* 当前索引表项指向的媒体数据的第一个字节到下一个索引表项指向的第一个字节所经过的时间长度,也就是两个索引项的时间间隔 */
  GstClockTime duration;
  gboolean starts_with_sap;
  guint8 sap_type;
  guint32 sap_delta_time; /* 相邻两个SAP之间的时间差。 */

  guint64 offset; /* 当前索引项相对于第一个索引项的相对偏移 */
  GstClockTime pts; /* 片段的时间戳 */
} GstSidxBoxEntry;

typedef struct _GstSidxBox
{
  guint8 version;
  guint32 flags;

  guint32 ref_id;
  guint32 timescale;
  guint64 earliest_pts;
  guint64 first_offset;

  gint entry_index;
  gint entries_count;

  GstSidxBoxEntry *entries;
} GstSidxBox;

typedef enum _GstSidxParserStatus
{
  GST_ISOFF_QT_SIDX_PARSER_INIT,
  GST_ISOFF_QT_SIDX_PARSER_HEADER,
  GST_ISOFF_QT_SIDX_PARSER_DATA,
  GST_ISOFF_QT_SIDX_PARSER_FINISHED
} GstSidxParserStatus;

typedef struct _GstSidxParser
{
  GstSidxParserStatus status;

  guint64 size;
  guint64 cumulative_entry_size; /* 累计片段大小 */
  guint64 cumulative_pts; /* 累计时间戳 */

  GstSidxBox sidx;
} GstSidxParser;

        SEEK的流程与旧的DASH/HLS类似,都是先SEEK STREAM,然后再用SEEK到的PTS,对所有流进行SEEK,如下介绍DASH MP4 SEEK相关的参数获取过程。

static GstFlowReturn
gst_dash_demux_stream_data_received (GstAdaptiveDemux2Stream * stream,
    GstBuffer * buffer)
{
  GstDashDemux2Stream *dash_stream = (GstDashDemux2Stream *) stream;
  GstFlowReturn ret = GST_FLOW_OK;
  guint index_header_or_data;
  /* 当前流需要下载index片断并且index的片断下载URL也存在,这种case下index片断和Movie分开存放,header和index 同时存在,优生下载index */
  if (stream->downloading_index)
    index_header_or_data = 1;
/* 当前流需要下载header片断并且header的片断下载URL也存在,像MP4这种格式,是需要下载header的,header和movie的内容分开存放 */
  else if (stream->downloading_header)
    index_header_or_data = 2;
  /* 下载数据 */
  else
    index_header_or_data = 3;
  /* 下载的数据类型改变 */
  if (dash_stream->current_index_header_or_data != index_header_or_data) {
    /* Clear pending data */
    if (gst_adapter_available (dash_stream->adapter) != 0)
      GST_ERROR_OBJECT (stream,
          "Had pending SIDX data after switch between index/header/data");
    gst_adapter_clear (dash_stream->adapter);
    dash_stream->current_index_header_or_data = index_header_or_data;
    dash_stream->current_offset = -1;
  }
  /* 流的偏移设置成buffer的偏移 */
  if (dash_stream->current_offset == -1)
    dash_stream->current_offset =
        GST_BUFFER_OFFSET_IS_VALID (buffer) ? GST_BUFFER_OFFSET (buffer) : 0;

  gst_adapter_push (dash_stream->adapter, buffer);
  buffer = NULL;
  /* 是MP4格式或者正在下载index */
  if (dash_stream->is_isobmff || stream->downloading_index) {
    /* SIDX index is also ISOBMMF */
    ret = gst_dash_demux_stream_handle_isobmff (stream);
  /* 索引表parse完成,代表实际索引项的数目等于文件中描述数目,初始值是GST_ISOFF_SIDX_PARSER_INIT,目前只有mp4才能GST_ISOFF_SIDX_PARSER_FINISHED,所以这段应该走不到 */
  } else if (dash_stream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED) {
    gsize available;

    /* Not ISOBMFF but had a SIDX index. Does this even exist or work?,这一段是取数据,推向后端的buffer */
    while (ret == GST_FLOW_OK
        && ((available = gst_adapter_available (dash_stream->adapter)) > 0)) {
      gboolean advance = FALSE;
      /* 如果当前索引是最后一个索引项,流的结束位置,流总在sidx之后开始 */
      guint64 sidx_end_offset =
          dash_stream->sidx_base_offset +
          SIDX_CURRENT_ENTRY (dash_stream)->offset +
          SIDX_CURRENT_ENTRY (dash_stream)->size;
      gboolean has_next = gst_dash_demux_stream_has_next_subfragment (stream);
      /* 流还没有下载完 */
      if (dash_stream->current_offset + available < sidx_end_offset) {
        buffer = gst_adapter_take_buffer (dash_stream->adapter, available);
      } else {/* 没有下一个列表并且下载的数据长度已经比索引表中给出的长度长 */
        if (!has_next && sidx_end_offset <= dash_stream->current_offset) {
          /* Drain all bytes, since there might be trailing bytes at the end of subfragment */
          buffer = gst_adapter_take_buffer (dash_stream->adapter, available);
        } else {
          if (sidx_end_offset <= dash_stream->current_offset) {
            /* This means a corrupted stream or a bug: ignoring bugs, it
             * should only happen if the SIDX index is corrupt */
            GST_ERROR_OBJECT (stream, "Invalid SIDX state");
            gst_adapter_clear (dash_stream->adapter);
            ret = GST_FLOW_ERROR;
            break;
          } else {
            buffer =
                gst_adapter_take_buffer (dash_stream->adapter,
                sidx_end_offset - dash_stream->current_offset);
            advance = TRUE;
          }
        }
      }

      GST_BUFFER_OFFSET (buffer) = dash_stream->current_offset;
      GST_BUFFER_OFFSET_END (buffer) =
          GST_BUFFER_OFFSET (buffer) + gst_buffer_get_size (buffer);
      dash_stream->current_offset = GST_BUFFER_OFFSET_END (buffer);

      ret = gst_adaptive_demux2_stream_push_buffer (stream, buffer);
      /* 准备下载下一个 */
      if (advance) {
        if (has_next) {
          GstFlowReturn new_ret;
          new_ret =
              gst_adaptive_demux2_stream_advance_fragment (stream,
              SIDX_CURRENT_ENTRY (dash_stream)->duration);

          /* only overwrite if it was OK before */
          if (ret == GST_FLOW_OK)
            ret = new_ret;
        } else {
          break;
        }
      }
    }
  } else {
    /* this should be the main header, just push it all */
    buffer = gst_adapter_take_buffer (dash_stream->adapter,
        gst_adapter_available (dash_stream->adapter));

    GST_BUFFER_OFFSET (buffer) = dash_stream->current_offset;
    GST_BUFFER_OFFSET_END (buffer) =
        GST_BUFFER_OFFSET (buffer) + gst_buffer_get_size (buffer);
    dash_stream->current_offset = GST_BUFFER_OFFSET_END (buffer);

    ret = gst_adaptive_demux2_stream_push_buffer (stream, buffer);
  }

  return ret;
}

static GstFlowReturn
gst_dash_demux_parse_isobmff (GstAdaptiveDemux * demux,
    GstDashDemux2Stream * dash_stream, gboolean * sidx_seek_needed)
{
……
  *sidx_seek_needed = FALSE;
……
  /* While there are more boxes left to parse ... */
  dash_stream->isobmff_parser.current_start_offset = buffer_offset;
  /* parse流里面的媒体信息,SIDX->MOOF->MDAT,然后退出,parse到sidx完成,sidx_seek_needed设置成TRUE,设置成TRUE时,parser里面所有的索引表信息,sync信息也会全部删除。 */
  do {
    dash_stream->isobmff_parser.current_fourcc = 0;
    dash_stream->isobmff_parser.current_size = 0;
    /* 如果reader中的不足以解析header中的信息,退出,size是包括头部,类型的全部BOX大小 */
    if (!gst_isoff_parse_box_header (&reader, &fourcc, NULL, &header_size,
            &size)) {
      break;
    }

    dash_stream->isobmff_parser.current_fourcc = fourcc;
    if (size == 0) {
      /* We assume this is mdat, anything else with "size until end"
       * does not seem to make sense */
      g_assert (dash_stream->isobmff_parser.current_fourcc ==
          GST_ISOFF_FOURCC_MDAT);
      dash_stream->isobmff_parser.current_size = -1;
      break;
    }
    dash_stream->isobmff_parser.current_size = size;
    /* Do we have the complete box or are at MDAT */
    if (gst_byte_reader_get_remaining (&reader) < size - header_size ||
        dash_stream->isobmff_parser.current_fourcc == GST_ISOFF_FOURCC_MDAT) {
      /* Reset byte reader to the beginning of the box */
      gst_byte_reader_set_pos (&reader,
          gst_byte_reader_get_pos (&reader) - header_size);
      break;
    }
……  /* 当前是数据部分,解析获取MOOF BOX的信息并且计算moof的平均size */
    if (dash_stream->isobmff_parser.current_fourcc == GST_ISOFF_FOURCC_MOOF) {
      GstByteReader sub_reader;

      /* Only allow SIDX before the very first moof */
      dash_stream->allow_sidx = FALSE;
      g_assert (dash_stream->moof == NULL);
      g_assert (dash_stream->moof_sync_samples == NULL);
      /* 读取数据的时候,先获取moof信息:
       * MOOF代表“Movie Fragment Box”,通常用于将MP4文件分成多个片段,以便更好地管理和传输视频和音频数据。
       * MOOF BOX由两个部分组成:mfhd和traf。
       * mfhd BOX是固定长度的BOX,用于存储片段序列号,片段序列号是一个递增的无符号32位数,用于标识每个MOOF BOX的唯一性。
       * traf BOX是MOOF BOX的第二部分,它包含有关特定片段的其他信息。traf BOX由四个子 BOX组成:tfhd BOX、tfdt BOX、trun BOX和其他可选 BOX。
       *   tfhd BOX:是“Track Fragment Header Box”的缩写,包含有关特定片段的信息,如轨道ID、样本描述符索引和默认样本持续时间等。
       *   tfdt BOX:是“Track Fragment Base Media Decode Time Box”的缩写,包含基本媒体解码时间的信息,以便于播放器正确地渲染视频和音频数据。
       *   trun BOX:是“Track Fragment Run Box”的缩写,包含有关特定片段的样本信息,如样本偏移量、持续时间、大小和标志等。
       *   其他可选 BOX:traf BOX还可以包含其他可选 BOX,如saiz BOX、saio BOX和subs BOX等,它们提供有关特定片段的其他信息。
       */
      gst_byte_reader_get_sub_reader (&reader, &sub_reader, size - header_size);
      dash_stream->moof = gst_isoff_moof_box_parse (&sub_reader);
      dash_stream->moof_offset =
          dash_stream->isobmff_parser.current_start_offset;
      dash_stream->moof_size = size;
      dash_stream->current_sync_sample = -1;
      /* 平均moof本身的大小 */
      if (dash_stream->moof_average_size) {
        if (dash_stream->moof_average_size < size)
          dash_stream->moof_average_size =
              (size * 3 + dash_stream->moof_average_size) / 4;
        else
          dash_stream->moof_average_size =
              (size + dash_stream->moof_average_size + 3) / 4;
      } else {
        dash_stream->moof_average_size = size;
      }
      /* 当前是SIDX并且是点播和allow sidx,allow_sidx是在segment改变以后,或者要切换到下一个片断或者bitrate改变切片源置成TRUE, 遇到SIDX或者MDAT之后置成FALSE。也就是需要重建索引表的时候置成TRUE */
    } else if (dash_stream->isobmff_parser.current_fourcc ==
        GST_ISOFF_FOURCC_SIDX &&
        gst_mpd_client2_has_isoff_ondemand_profile (dashdemux->client) &&
        dash_stream->allow_sidx) {
      GstByteReader sub_reader;
      GstIsoffParserResult res;
      guint dummy;

      dash_stream->sidx_base_offset =
          dash_stream->isobmff_parser.current_start_offset + size;
      dash_stream->allow_sidx = FALSE;

      gst_byte_reader_get_sub_reader (&reader, &sub_reader, size - header_size);
      /* Parse sidx的全部信息,gst_isoff_sidx_parser_parse里面的case 没有break,会全部parse出所有信息。 */
      res =
          gst_isoff_sidx_parser_parse (&dash_stream->sidx_parser, &sub_reader,
          &dummy);

      if (res == GST_ISOFF_PARSER_DONE) {
        guint64 first_offset = dash_stream->sidx_parser.sidx.first_offset;
        GstSidxBox *sidx = SIDX (dash_stream);
        guint i;

        if (first_offset) {
          GST_LOG_OBJECT (stream,
              "non-zero sidx first offset %" G_GUINT64_FORMAT, first_offset);
          dash_stream->sidx_base_offset += first_offset;
        }

        for (i = 0; i < sidx->entries_count; i++) {
          GstSidxBoxEntry *entry = &sidx->entries[i];

          if (entry->ref_type != 0) {
            GST_FIXME_OBJECT (stream, "SIDX ref_type 1 not supported yet");
            dash_stream->sidx_position = GST_CLOCK_TIME_NONE;
            gst_isoff_sidx_parser_clear (&dash_stream->sidx_parser);
            break;
          }
        }

        /* We might've cleared the index above,parse到索引表之后SEEK */
        if (sidx->entries_count > 0) {
          if (GST_CLOCK_TIME_IS_VALID (dash_stream->pending_seek_ts)) {
            /* FIXME, preserve seek flags, 有pending pts的时候,需要seek到pending pts对应的时间点 */
            if (gst_dash_demux_stream_sidx_seek (dash_stream,
                    demux->segment.rate >= 0, 0, dash_stream->pending_seek_ts,
                    NULL) != GST_FLOW_OK) {
              GST_ERROR_OBJECT (stream, "Couldn't find position in sidx");
              dash_stream->sidx_position = GST_CLOCK_TIME_NONE;
              gst_isoff_sidx_parser_clear (&dash_stream->sidx_parser);
            }
            dash_stream->pending_seek_ts = GST_CLOCK_TIME_NONE;
          /* 无Pending pts的时候,parse完sidx之后,seek sidx到sidx_position */
          } else {

            if (dash_stream->sidx_position == GST_CLOCK_TIME_NONE) {
              SIDX (dash_stream)->entry_index = 0;
            } else {
              if (gst_dash_demux_stream_sidx_seek (dash_stream,
                      demux->segment.rate >= 0, GST_SEEK_FLAG_SNAP_BEFORE,
                      dash_stream->sidx_position, NULL) != GST_FLOW_OK) {
                GST_ERROR_OBJECT (stream, "Couldn't find position in sidx");
                dash_stream->sidx_position = GST_CLOCK_TIME_NONE;
                gst_isoff_sidx_parser_clear (&dash_stream->sidx_parser);
              }
            }
            /* SEEK的时候,已经改变过entry_index */
            dash_stream->sidx_position =
                SIDX (dash_stream)->entries[SIDX (dash_stream)->entry_index].
                pts;
          }
        }
        if (dash_stream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED &&
            SIDX (dash_stream)->entry_index != 0) {
          /* Need to jump to the requested SIDX entry. Push everything up to
           * the SIDX box below and let the caller handle everything else */
          *sidx_seek_needed = TRUE;
          break;
        }
      }
    } else {
      gst_byte_reader_skip (&reader, size - header_size);
    }

    dash_stream->isobmff_parser.current_fourcc = 0;
    dash_stream->isobmff_parser.current_start_offset += size;
    dash_stream->isobmff_parser.current_size = 0;
  } while (gst_byte_reader_get_remaining (&reader) > 0);

  gst_buffer_unmap (buffer, &map);

  /* mdat? Push all we have and wait for it to be over,把余下的MDAT的数据全部Push给后面的元素。 */
  if (dash_stream->isobmff_parser.current_fourcc == GST_ISOFF_FOURCC_MDAT) {
    GstBuffer *pending;

    GST_LOG_OBJECT (stream,
        "box %" GST_FOURCC_FORMAT " at offset %" G_GUINT64_FORMAT " size %"
        G_GUINT64_FORMAT, GST_FOURCC_ARGS (fourcc),
        dash_stream->isobmff_parser.current_start_offset,
        dash_stream->isobmff_parser.current_size);

    /* At mdat. Move the start of the mdat to the adapter and have everything
     * else be pushed. We parsed all header boxes at this point and are not
     * supposed to be called again until the next moof */
    pending = _gst_buffer_split (buffer, gst_byte_reader_get_pos (&reader), -1);
    gst_adapter_push (dash_stream->adapter, pending);
    dash_stream->current_offset += gst_byte_reader_get_pos (&reader);
    dash_stream->isobmff_parser.current_size = 0;

    GST_BUFFER_OFFSET (buffer) = buffer_offset;
    GST_BUFFER_OFFSET_END (buffer) =
        buffer_offset + gst_buffer_get_size (buffer);
    return gst_adaptive_demux2_stream_push_buffer (stream, buffer);
  } else if (gst_byte_reader_get_pos (&reader) != 0) {
    GstBuffer *pending;

    /* Multiple complete boxes and no mdat? Push them and keep the remainder,
     * which is the start of the next box if any remainder */

    pending = _gst_buffer_split (buffer, gst_byte_reader_get_pos (&reader), -1);
    gst_adapter_push (dash_stream->adapter, pending);
    dash_stream->current_offset += gst_byte_reader_get_pos (&reader);
    dash_stream->isobmff_parser.current_size = 0;

    GST_BUFFER_OFFSET (buffer) = buffer_offset;
    GST_BUFFER_OFFSET_END (buffer) =
        buffer_offset + gst_buffer_get_size (buffer);
    return gst_adaptive_demux2_stream_push_buffer (stream, buffer);
  }

  /* Not even a single complete, non-mdat box, wait */
  dash_stream->isobmff_parser.current_size = 0;
  gst_adapter_push (dash_stream->adapter, buffer);

  return GST_FLOW_OK;
}

static GstFlowReturn
gst_dash_demux_stream_handle_isobmff (GstAdaptiveDemux2Stream * stream)
{
……
  /* We parse all ISOBMFF boxes of a (sub)fragment until the mdat. This covers
   * at least moov, moof and sidx boxes. Once mdat is received we just output
   * everything until the next (sub)fragment,一直parse,直到数据为ES的样本数据为止 */
  if (dash_stream->isobmff_parser.current_fourcc != GST_ISOFF_FOURCC_MDAT) {
    gboolean sidx_seek_needed = FALSE;

    ret = gst_dash_demux_parse_isobmff (demux, dash_stream, &sidx_seek_needed);
    if (ret != GST_FLOW_OK)
      return ret;

    /* Go to selected segment if needed here */
    if (sidx_seek_needed && !stream->downloading_index)
      return GST_ADAPTIVE_DEMUX_FLOW_END_OF_FRAGMENT;

    /* No mdat yet, let's get called again with the next boxes */
    if (dash_stream->isobmff_parser.current_fourcc != GST_ISOFF_FOURCC_MDAT)
      return ret;

    /* Here we end up only if we're right at the mdat start */

    /* Jump to the next sync sample. As we're doing chunked downloading
     * here, just drop data until our chunk is over so we can reuse the
     * HTTP connection instead of having to create a new one or
     * reuse the data if the sync sample follows the moof */
    if (dash_stream->active_stream->mimeType == GST_STREAM_VIDEO
        && gst_dash_demux_find_sync_samples (demux, stream) &&
        GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux)) {
      guint idx = -1;
      gboolean playing_forward = (demux->segment.rate > 0.0);

      if (GST_CLOCK_TIME_IS_VALID (dash_stream->target_time)) {
        idx =
            (dash_stream->target_time -
            dash_stream->current_fragment_timestamp) /
            dash_stream->current_fragment_keyframe_distance;
      } else if (playing_forward) {
        idx = 0;
      }

      GST_DEBUG_OBJECT (stream,
          "target %" GST_TIME_FORMAT " idx %d",
          GST_TIME_ARGS (dash_stream->target_time), idx);
      /* Figure out target time */

      if (dash_stream->first_sync_sample_after_moof && idx == 0) {
        /* If we're here, don't throw away data but collect sync
         * sample while we're at it below. We're doing chunked
         * downloading so might need to adjust the next chunk size for
         * the remainder */
        dash_stream->current_sync_sample = 0;
        GST_DEBUG_OBJECT (stream, "Using first keyframe after header");
      }
    }

    if (gst_adapter_available (dash_stream->adapter) == 0)
      return ret;

    /* We have some data from the mdat available in the adapter, handle it
     * below in the push code */
  } else {
    /* Somewhere in the middle of the mdat */
  }

  /* At mdat */
  if (dash_stream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED) {
    guint64 sidx_end_offset =
        dash_stream->sidx_base_offset +
        SIDX_CURRENT_ENTRY (dash_stream)->offset +
        SIDX_CURRENT_ENTRY (dash_stream)->size;
    gboolean has_next = gst_dash_demux_stream_has_next_subfragment (stream);
    gsize available;

    /* Need to handle everything in the adapter according to the parsed SIDX
     * and advance subsegments accordingly */

    available = gst_adapter_available (dash_stream->adapter);
    if (dash_stream->current_offset + available < sidx_end_offset) {
      buffer = gst_adapter_take_buffer (dash_stream->adapter, available);
    } else if (!has_next && sidx_end_offset <= dash_stream->current_offset) {
      /* Drain all bytes, since there might be trailing bytes at the end of subfragment */
      buffer = gst_adapter_take_buffer (dash_stream->adapter, available);
    } else if (sidx_end_offset <= dash_stream->current_offset) {
      /* This means a corrupted stream or a bug: ignoring bugs, it
       * should only happen if the SIDX index is corrupt */
      GST_ERROR_OBJECT (stream, "Invalid SIDX state. "
          " sidx_end_offset %" G_GUINT64_FORMAT " current offset %"
          G_GUINT64_FORMAT, sidx_end_offset, dash_stream->current_offset);
      gst_adapter_clear (dash_stream->adapter);
      return GST_FLOW_ERROR;
    } else {
      buffer =
          gst_adapter_take_buffer (dash_stream->adapter,
          sidx_end_offset - dash_stream->current_offset);
      sidx_advance = TRUE;
    }
  } else {
    /* Take it all and handle it further below */
    buffer =
        gst_adapter_take_buffer (dash_stream->adapter,
        gst_adapter_available (dash_stream->adapter));

    /* Attention: All code paths below need to update dash_stream->current_offset */
  }

  /* We're actually running in key-units trick mode */
  if (dash_stream->active_stream->mimeType == GST_STREAM_VIDEO
      && dash_stream->moof_sync_samples
      && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux)) {
    if (dash_stream->current_sync_sample == -1) {
      /* We're doing chunked downloading and wait for finishing the current
       * chunk so we can jump to the first keyframe */
      dash_stream->current_offset += gst_buffer_get_size (buffer);
      gst_buffer_unref (buffer);
      return GST_FLOW_OK;
    } else {
      GstDashStreamSyncSample *sync_sample =
          &g_array_index (dash_stream->moof_sync_samples,
          GstDashStreamSyncSample, dash_stream->current_sync_sample);
      guint64 end_offset =
          dash_stream->current_offset + gst_buffer_get_size (buffer);

      /* Make sure to not download too much, this should only happen for
       * the very first keyframe if it follows the moof */
      if (dash_stream->current_offset >= sync_sample->end_offset + 1) {
        dash_stream->current_offset += gst_buffer_get_size (buffer);
        gst_buffer_unref (buffer);
        return GST_FLOW_OK;
      } else if (end_offset > sync_sample->end_offset + 1) {
        guint64 remaining =
            sync_sample->end_offset + 1 - dash_stream->current_offset;
        GstBuffer *sub = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL, 0,
            remaining);
        gst_buffer_unref (buffer);
        buffer = sub;
      }
    }
  }

  GST_BUFFER_OFFSET (buffer) = dash_stream->current_offset;
  dash_stream->current_offset += gst_buffer_get_size (buffer);
  GST_BUFFER_OFFSET_END (buffer) = dash_stream->current_offset;

  ret = gst_adaptive_demux2_stream_push_buffer (stream, buffer);
  if (ret != GST_FLOW_OK)
    return ret;

  if (sidx_advance) {
    ret =
        gst_adaptive_demux2_stream_advance_fragment (stream,
        SIDX_CURRENT_ENTRY (dash_stream)->duration);
    if (ret != GST_FLOW_OK)
      return ret;

    /* If we still have data available, recurse and use it up if possible */
    if (gst_adapter_available (dash_stream->adapter) > 0)
      return gst_dash_demux_stream_handle_isobmff (stream);
  }

  return ret;
}


 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这段代码是在Docker容器中执行的一系列命令,用于安装一些软件包和依赖项。具体来说,它执行以下操作: 1. `apt-get clean`:清理apt-get缓存,以释放磁盘空间。 2. `apt-get update`:更新apt-get软件包列表。 3. `apt-get install -y`:安装以下软件包和依赖项: - `python3`:Python 3的主要二进制文件。 - `python3-pip`:Python 3的包管理工具pip。 - `libopencv-dev`:OpenCV开发库的头文件和静态库。 - `python3-opencv`:Python 3的OpenCV绑定。 - `build-essential`:构建软件包所需的基本工具和编译器。 - `yasm`:视频编解码器的汇编器。 - `cmake`:跨平台的构建工具。 - `libtool`:通用库支持脚本工具。 - `libc6`、`libc6-dev`:C标准库的运行时库和开发文件。 - `unzip`:解压缩工具。 - `wget`:网络下载工具。 - `libnuma1`、`libnuma-dev`:NUMA(非统一内存访问)系统的库和开发文件。 - `libgstreamer1.0-0`:GStreamer多媒体框架的核心库。 - `gstreamer1.0-plugins-base`、`gstreamer1.0-plugins-good`、`gstreamer1.0-plugins-bad`、`gstreamer1.0-plugins-ugly`、`gstreamer1.0-libav`:GStreamer插件和解码器。 - `gstreamer1.0-doc`、`gstreamer1.0-tools`、`gstreamer1.0-x`、`gstreamer1.0-alsa`、`gstreamer1.0-gl`、`gstreamer1.0-gtk3`、`gstreamer1.0-qt5`、`gstreamer1.0-pulseaudio`:GStreamer的文档、工具和相关库。 - `libglib2.0-dev`:GLib开发库的头文件。 - `libgstrtspserver-1.0-dev`:GStreamer RTSP服务器库的开发文件。 - `gstreamer1.0-rtsp`:GStreamer的RTSP插件。 这些操作旨在为容器配置一个适合开发的环境,使其能够支持Python编程、OpenCV图像处理和GStreamer多媒体处理等任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值