Skip to content

DeliveryHLS

winlin edited this page Mar 7, 2014 · 32 revisions

分发方式:HLS

SRS支持HLS/RTMP两种成熟而且广泛应用的流媒体分发方式。

RTMP指Adobe的RTMP(Realtime Message Protocol),广泛应用于低延时直播,也是编码器和服务器对接的实际标准协议,在PC(Flash)上有最佳观看体验和最佳稳定性。

HLS指Apple的HLS(Http Live Streaming),本身就是Live(直播)的,不过Vod(点播)也能支持。HLS是Apple平台的标准流媒体协议,和RTMP在PC上一样支持得天衣无缝。

HLS和RTMP两种分发方式,就可以支持所有的终端。

分发方式比较

详见下表:

分发 平台 协议 公司 说明
RTMP Windows Flash RTMP Adobe 主流的低延时分发方式,
Adobe对RTMP是Flash原生支持方式,
FMS(Adobe Media Server前身),
就是Flash Media Server的简写,可见Flash播放RTMP是多么“原生”,
就像浏览器打开http网页一样“原生”,
经测试,Flash播放RTMP流可以10天以上不间断播放。
HLS Apple/Android HTTP Apple/Google 延时一个切片以上(一般10秒以上),
Apple平台上HLS的效果比PC的RTMP还要好,
而且Apple所有设备都支持,
Android最初不支持HLS,后来也支持了,
但测试发现支持得还不如Apple,
不过观看是没有问题,稳定性稍差,
所以有些公司专门做Android上的流媒体播放器。
HDS - HTTP Adobe Adobe自己的HLS,
协议方面做得是复杂而且没有什么好处,
国内没有什么应用,传说国外有,
像这种东西SRS是绝对不会支持的。
DASH - HTTP - Dynamic Adaptive Streaming over HTTP (DASH),
一些公司提出的HLS,
国内还没有应用,国外据说有用了,
nginx-rtmp好像已经支持了,
明显这个还不成熟,SRS是不会支持的。

HLS简介

HLS是提供一个m3u8地址,Apple的Safari浏览器直接就能打开m3u8地址,譬如:

http://demo.srs.com/live/livestream.m3u8

Android不能直接打开,需要使用html5的video标签,然后在浏览器中打开这个页面即可,譬如:

<!-- livestream.html -->
<video width="640" height="360"
        autoplay controls autobuffer 
        src="http://demo.srs.com/live/livestream.m3u8"
        type="application/vnd.apple.mpegurl">
</video>

HLS的m3u8,是一个ts的列表,也就是告诉浏览器可以播放这些ts文件,譬如:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:64
#EXT-X-TARGETDURATION:12
#EXTINF:11.550
livestream-64.ts
#EXTINF:5.250
livestream-65.ts
#EXTINF:7.700
livestream-66.ts
#EXTINF:6.850
livestream-67.ts

有几个关键的参数,这些参数在SRS的配置文件中都有配置项: × EXT-X-TARGETDURATION:所有切片的最大时长。有些Apple设备这个参数不正确会无法播放。SRS会自动计算出ts文件的最大时长,然后更新m3u8时会自动更新这个值。用户不必自己配置。

  • EXTINF:ts切片的实际时长,SRS提供配置项hls_fragment,但实际上的ts时长还受gop影响,详见下面配置HLS的说明。
  • ts文件的数目:SRS可配置hls_window,指定m3u8中保存多少个切片,SRS会自动清理旧的切片。
  • livestream-67.ts:SRS会自动维护ts切片的文件名,在编码器重推之后,这个编号会继续增长,保证流的连续性。直到SRS重启,这个编号才重置为0。

譬如,每个ts切片为10秒,窗口为60秒,那么m3u8中会保存6个ts切片。

HLS流程

HLS的主要流程是:

  1. FFMPEG或FMLE或编码器,推送RTMP流到SRS,编码为H264/AAC(其他编码需要SRS转码)
  2. SRS将RTMP切片成TS,并生成M3U8。若流非H264和AAC,则停止输出HLS(可使用SRS转码到SRS其他vhost或流,然后再切HLS)。
  3. 访问m3u8,nginx提供HTTP服务。
    注意:SRS只需要在Vhost上配置HLS,会自动根据流的app创建目录

配置方法

conf/srs.conf中的with-hls.vhost.com是HLS配置的实例,可以拷贝到默认的Vhost,例如:

vhost __defaultVhost__ {
    hls {
        # whether the hls is enabled.
        # if off, donot write hls(ts and m3u8) when publish.
        # default: off
        enabled         on;
        # the hls output path.
        # the app dir is auto created under the hls_path.
        # for example, for rtmp stream:
        #   rtmp://127.0.0.1/live/livestream
        #   http://127.0.0.1/live/livestream.m3u8
        # where hls_path is /hls, srs will create the following files:
        #   /hls/live       the app dir for all streams.
        #   /hls/live/livestream.m3u8   the HLS m3u8 file.
        #   /hls/live/livestream-1.ts   the HLS media/ts file.
        # in a word, the hls_path is for vhost.
        # default: ./objs/nginx/html
        hls_path        /data/nginx/html;
        # the hls fragment in seconds, the duration of a piece of ts.
        # default: 10
        hls_fragment    10;
        # the hls window in seconds, the number of ts in m3u8.
        # default: 60
        hls_window      60;
    }
}

其中hls配置就是HLS的配置,主要配置项如下:

  • enabled:是否开启HLS,on/off,默认off。
  • hls_path:HLS的m3u8和ts文件保存的路径。SRS会自动加上app和stream名称。譬如:
对于RTMP流:rtmp://localhost/live/livestream
HLS配置路径:hls_path        /data/nginx/html;
那么会生成以下文件:
/data/nginx/html/live/livestream.m3u8
/data/nginx/html/live/livestream-0.ts
/data/nginx/html/live/livestream-1.ts
/data/nginx/html/live/livestream-2.ts
最后的HLS地址为:http://localhost/live/livestream.m3u8
  • hls_fragment:秒,指定ts切片的最小长度。实际上ts文件的长度由以下公式决定:
ts文件时长 = max(hls_fragment, gop_size)
hls_fragment:配置文件中的长度。譬如:5秒。
gop_size:编码器配置的gop的长度,譬如ffmpeg指定fps为20帧/秒,gop为200帧,则gop_size=gop/fps=10秒。
那么,最终ts的时长为max(5, 10) = 10秒。这也是为什么有些流配置了hls_fragment,但是ts时长仍然比这个大的原因。
  • hls_window:秒,指定HLS窗口大小,即m3u8中ts文件的时长之和,超过总时长后,丢弃第一个m3u8中的第一个切片,直到ts的总时长在这个配置项范围之内。即SRS保证下面的公式:
hls_window >= sum(m3u8中每个ts的时长)

HLS和Forward

Forward的流和普通流不做区分,若forward的流所在的VHOST配置了HLS,一样会应用HLS配置进行切片。

因此,可以对原始流进行Transcode之后,保证流符合h.264/aac的规范,然后forward到多个配置了HLS的VHOST进行切片。支持多个源站的热备。

HLS和Transcode

Transcode将RTMP流转码后,可以让SRS接入任何编码的RTMP流,然后转换成HLS要求的h.264/aac编码方式。

配置Transcode时,若需要控制ts长度,需要配置ffmpeg编码的gop,譬如:

vhost hls.transcode.vhost.com {
    transcode {
        enabled     on;
        ffmpeg      ./objs/ffmpeg/bin/ffmpeg;
        engine hls {
            enabled         on;
            vfilter {
            }
            vcodec          libx264;
            vbitrate        500;
            vfps            20;
            vwidth          768;
            vheight         320;
            vthreads        2;
            vprofile        baseline;
            vpreset         superfast;
            vparams {
                g           100;
            }
            acodec          libaacplus;
            abitrate        45;
            asample_rate    44100;
            achannels       2;
            aparams {
            }
            output          rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine];
        }
    }
}

该FFMPEG转码参数,指定gop时长为100/20=5秒,fps帧率(vfps=20),gop帧数(g=100)。

SRS如何支持HLS

SRS的HLS主要参考了nginx-rtmp的HLS实现方式,SRS没有做什么事情,都是nginx-rtmp实现的。而分发m3u8和ts文件,也是使用nginx分发的。

SRS只是读了遍ts的标准文档,把相关部分加了注释而已。譬如下面这段:

// @see: ngx_rtmp_mpegts.c
// TODO: support full mpegts feature in future.
class SrsMpegtsWriter
{
public:
    static int write_frame(int fd, SrsMpegtsFrame* frame, SrsCodecBuffer* buffer)
    {
        int ret = ERROR_SUCCESS;
        
        if (!buffer->bytes || buffer->size <= 0) {
            return ret;
        }
        
        char* last = buffer->bytes + buffer->size;
        char* pos = buffer->bytes;
        
        bool first = true;
        while (pos < last) {
            static char packet[188];
            char* p = packet;
            
            frame->cc++;
            
            // sync_byte; //8bits
            *p++ = 0x47;
            // pid; //13bits
            *p++ = (frame->pid >> 8) & 0x1f;
            // payload_unit_start_indicator; //1bit
            if (first) {
                p[-1] |= 0x40;
            }
            *p++ = frame->pid;
            
            // transport_scrambling_control; //2bits
            // adaption_field_control; //2bits, 0x01: PayloadOnly
            // continuity_counter; //4bits
            *p++ = 0x10 | (frame->cc & 0x0f);
            
            if (first) {
                first = false;
                if (frame->key) {
                    p[-1] |= 0x20; // Both Adaption and Payload
                    *p++ = 7;    // size
                    *p++ = 0x50; // random access + PCR
                    p = write_pcr(p, frame->dts - SRS_HLS_DELAY);
                }
                
                // PES header
                // packet_start_code_prefix; //24bits, '00 00 01'
                *p++ = 0x00;
                *p++ = 0x00;
                *p++ = 0x01;
                //8bits
                *p++ = frame->sid;
                
                // pts(33bits) need 5bytes.
                u_int8_t header_size = 5;
                u_int8_t flags = 0x80; // pts
                
                // dts(33bits) need 5bytes also
                if (frame->dts != frame->pts) {
                    header_size += 5;
                    flags |= 0x40; // dts
                }
                
                // 3bytes: flag fields from PES_packet_length to PES_header_data_length
                int pes_size = (last - pos) + header_size + 3;
                if (pes_size > 0xffff) {
                    /**
                    * when actual packet length > 0xffff(65535),
                    * which exceed the max u_int16_t packet length,
                    * use 0 packet length, the next unit start indicates the end of packet.
                    */
                    pes_size = 0;
                }
                
                // PES_packet_length; //16bits
                *p++ = (pes_size >> 8);
                *p++ = pes_size;
                
                // PES_scrambling_control; //2bits, '10'
                // PES_priority; //1bit
                // data_alignment_indicator; //1bit
                // copyright; //1bit
                // original_or_copy; //1bit	
                *p++ = 0x80; /* H222 */
                
                // PTS_DTS_flags; //2bits
                // ESCR_flag; //1bit
                // ES_rate_flag; //1bit
                // DSM_trick_mode_flag; //1bit
                // additional_copy_info_flag; //1bit
                // PES_CRC_flag; //1bit
                // PES_extension_flag; //1bit
                *p++ = flags;
                
                // PES_header_data_length; //8bits
                *p++ = header_size;

                // pts; // 33bits
                p = write_pts(p, flags >> 6, frame->pts + SRS_HLS_DELAY);
                
                // dts; // 33bits
                if (frame->dts != frame->pts) {
                    p = write_pts(p, 1, frame->dts + SRS_HLS_DELAY);
                }
            }
            
            int body_size = sizeof(packet) - (p - packet);
            int in_size = last - pos;
            
            if (body_size <= in_size) {
                memcpy(p, pos, body_size);
                pos += body_size;
            } else {
                p = fill_stuff(p, packet, body_size, in_size);
                memcpy(p, pos, in_size);
                pos = last;
            }
            
            // write ts packet
            if (::write(fd, packet, sizeof(packet)) != sizeof(packet)) {
                ret = ERROR_HLS_WRITE_FAILED;
                srs_error("write ts file failed. ret=%d", ret);
                return ret;
            }
        }
        
        return ret;
    }
};

Winlin 2014.2

Welcome to SRS wiki!

SRS 5.0 wiki

Please select your language:

SRS 4.0 wiki

Please select your language:

SRS 3.0 wiki

Please select your language:

SRS 2.0 wiki

Please select your language:

SRS 1.0 wiki

Please select your language:

Clone this wiki locally