天天看点

HLSHLS : http live streaming

HLS : http live streaming

主要关注点:

1: 把音视频的package 封装成TS流文件  2: m3u8 索引文件和分段策略

m3u8 格式详解:

注意看切片索引文件:

#EXTM3U                     m3u文件头,必须放在第一行
#EXT-X-MEDIA-SEQUENCE       第一个TS分片的序列号      
更新规则:      
移除一个文件播放列表中靠前的(认为已播放的)文件      
不断更新         EXT-X-MEDIA-SEQUENCE                标签,以步长为1进行递增      
#EXT-X-TARGETDURATION       每个分片TS的最大的时长      
#EXT-X-ALLOW-CACHE          是否允许cache      
如果播放列表文件包含了EXT-X-ALLOW-CATCH标签,并且它的值为NO,那么客户端在播放以后不可以缓存媒体文件。否则允许缓存用来以后重播。      
#EXT-X-ENDLIST 
      
如果不存在EXT-X-ENDLIST标签,表示此时是直播,并且客户端想正常播放媒体(按顺序以标准速率播放),那么客户端就从播放列表文件尾部选择三个target duration的媒体文件,进行下载播放。      
为什么要选择最后三个?   原因是因为,直播,客户端会默认播放最近的视频。      
 #EXTINF

           
指示出下面TS片的时间长度,单位是秒,可以是整数也可以浮点数,浮点数一般精确到小数点后面3位。 同时,EXTINF也影响了播放器刷新M3U8文件的间隔,正常情况下,播放器会把当前下载的TS片的EXTINF的值作为每次刷新M3U8文件的间隔;如果播放器发现本次取到的M3U8文件内容没有更新,会在1-2秒内再次刷新。
#EXT-X-ENDLIST
其中

#EXT-X-ENDLIST

为切片终止标记,如果没有该标记,浏览器会在文件读取完后再请求索引文件,如果有更新则继续下载新文件,以此达到直播效果。
EXT-X-PROGRAM-DATE-TIME      
客户端可以使用EXT-X-PROGRAM-DATE-TIME标签来为用户显示节目的起始时间。如果这个值包含了时区信息,那么客户端应该考虑到这点;如果不包含,那么客户端不可以推测时区。

例子:

#EXTM3U
#EXT-X-TARGETDURATION:11
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:10.133333,
fileSequence0.ts
#EXTINF:10.000666,
fileSequence1.ts
#EXT-X-ENDLIST
           
mpeg-ts流详解:      
typedef struct _tag_PACKET_HEADER 
{
    unsigned sync_byte: 8; <span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;">//同步字节,总是0x47(0100 0111),表示这个包是正确的ts包</span>
    
    unsigned transport_error_indicator: 1; <span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;">//传输错误指示符,1:暂且定义为本包有个无法修复的错误</span>
   
    unsigned payload_unit_start_indicator: 1; <span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;"> //负载单位开始指示符,1:PES或者PSI的开始部分,0:其他</span>

   
    unsigned transport_priority: 1; <span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;"> //传输优先指示符,1:比相同PID的包有更高的优先级</span>

   
    unsigned PID: 13;<span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;"> //Packet ID</span>

    //加扰控制指示符
    //00:未加扰(俗称清流);01:留着等以后使用
    //10:以even key方式加扰;11:以odd key方式加扰
    unsigned scrambling_control: 2;
    //适配区存在指示符
    //00:留着等以后使用;
    //01:本报不含适配区,只有负载区
    //10:本包只含适配区,没有负载区
    //11:本包不仅含有适配区,还含有负载区
    //其实可以把这两bit分开来理解,第一个bit指示是否有适配区,第二个指示是否有负载区
    unsigned adaptation_field_exist: 2;
    //当本包目前有负载区时,它才回递增
    //例如,当上面的字段为01or11时,本字段才回递增
    unsigned continuity_counter: 4;
} PACKET_HEADER;
以上就是header的结构,可见sizeof(PACKET_HEADER) == 4 Bytes

根据header的情况,我可以推测出剩余的PACKET_DATA最长只有184Bytes了
typedef _tag_PACKET_DATA {
  
    struct adaptation_field;<span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;">  //0或者更长,依据PACKET_HEADER.adaptation_field_exist的第一bit而定</span>

    struct payload_data<span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;"> ; //0或者更长,依据PACKET_HEADER.adaptation_field_exist的第二bit而定</span>
}

下面请看adaptation_field的结构定义:
typedef struct adaptation_field {
   
    adaptation_field_length: 8;<span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;"> //本区域除了本字节剩下的长度(不包含本字节!!!切记)</span>

    //不连续指示符
    //1:本ts包在continuity_counter或者PCR,是处于不连续状态的
    discontinuity_indicator: 1;
    //可随机访问指示符
    //1:本包中的PES包可以启动一个视频/音频序列
    //就是说如果是1,可以从这个包开始牵出该条ES流
    random_access_indicator: 1;
    //es流优先级指示符
    //1:当同pid时,该es流有更高级的优先权
    elementary_stream_priority_indicator: 1;
    //Program Clock Reference,含有“节目时钟参考”标志
    //1:适配区里面含有PCR field字段,0:不含有
    PCR_flag: 1;
    //1:适配区里面含有splice_countdown字段
    splicing_point_flag: 1;
    //1:适配区里面含有private_data_bytes字段
    trasport_private_data_flag: 1;
    //1:适配去里面含有adaptation_field_extension字段
    adaptation_field_extension_flag: 1;

    //下面几个字段依据上面几个指示器的值而存在与否

    //Program Clock Reference,共6个字节,48bits,大端模式存储!
    //33bits是基本的,6个填充bits,9个扩展bits
    //时间参考,至少约100ms会确定一次audio和video之间的同步问题
    PCR: 33+6+9;
    //Original Program Clock Reference
    //原始PCR,当一个ts包被复制到另一个包里面去了,这里保留其原始的PCR值
    OPCR: 33+6+9
    //分隔倒数器,我自己翻译的,求指点
    //表示当一个分割点出现时,从这个ts包起,还剩多少个ts包(有可能是负数)
    splice_countdown: 8;
    //填充字节,全0xFF,目的是为了使得每一个packet的长度为188Bytes
    //这个的结果可以使得码流为恒定值
    //当然,如果一次想携带的内容超过184字节了怎么办?那就得分隔成多个ts packet了
    stuffing_bytes: 不定长;
}
           
payload_data结构就是所携带的纯粹的数据了,遵守不足184Bytes就填充FF,超过184Bytes就分隔。
  
  

  
  
   所以ts包可以这样表示
  
  
   【header】+【data】 == 【188Bytes】
  
  
   【header】== 4 Bytes
  
  
   【data】 == 184 Bytes == 【adaptation_field】+【payload_data+填充】
  
  
   【adaptation_field】 == adaptation_field->adaptation_field_length
  
  
   【payload_data+填充】 == 184 - field.adaptation_field_length
  
      

HLS原理:   客户端:

1:首先下载m3u8文件,然后解析该文件 2:解析出每个ts文件的url地址,文件时长,文件序号。 3:然后,逐个下载ts文件。 4:将TS文件解码并进行播放。

客户端刷新列表的规则:  1:客户端首先会下载播放列表。 2:根据列表中,逐个下载ts文件。 3:并根据最后一个ts文件的时长的作为每次刷新M3U8文件的间隔 4:如果发现列表没有变化,继续等待,

第一次是0.5倍*ts文件的时长,第二次1.5倍,3倍。。。      

服务端:

如果是点播

1:首先对数据源文件进行解码 2:之后打包成一段一段的mpeg-ts数据,将该数据存成ts 文件 3:并将该文件时长信息以及url地址逐步写到m3u8文件里面。

如果是直播:

1:数据源将数据传输到服务器端之后,如果数据是裸流的话,直接打包成mpeg-ts文件 2:如果数据不是裸流(例如是YUV),就解码,然后打包成mpeg-ts文件 3:将该文件时长信息以及url地址逐步写到m3u8文件里面。

直播列表:

1:里面最少含有一个url,最多不超过3个。 2: 如果服务器期望移除演示文稿,它必须使播放列表文件对于客户端不可用,在播放列表被清除时,它应该确保播放列表文件中的所有媒体文件对于客户端来说至少在一个播放列表文件持续时间内是可用的。 3:那些不包含EXT-X-ENDLIST标签的播放列表文件的持续时间必须至少三倍于targrtdutration。