DeliveryHLS
分发方式: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切片。
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;
}
}; 配置方法
SRS的with-hls.vhost.comVHOST是HLS配置的实例:
the vhost with hls specified.
vhost with-hls.vhost.com { 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_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)。