CentOS6.x下配置sendmail发邮件

安装配置sendmail软件  

yum install -y sendmail sendmail-cf m4

设置Sendmail服务的网络访问权限

vi /etc/mail/sendmail.mc

DAEMON_OPTIONS(`Port=smtp,Addr=127.0.0.1, Name=MTA')dnl

将127.0.0.1改为0.0.0.0,意思是任何主机都可以访问Sendmail服务。如果仅让某一个网段能够访问到Sendmail服务,将127.0.0.1改为形如192.168.1.0/24的一个特定网段地址。

生成Sendmail配置文件

Sendmail的配置文件由m4来生成,m4工具在sendmail-cf包中。如果系统无法识别m4命令,说明sendmail-cf软件包没有安装。

生成Sendmail的配置文件:

m4 /etc/mail/sendmail.mc /etc/mail/sendmail.cf  

需要重启Sendmail才能使配置文件生效。

service sendmail restart

把机器名加入到/etc/hosts中

echo ""  >> /etc/hosts

echo "127.0.0.1      $HOSTNAME"  >> /etc/hosts

iptables配置

iptables -A INPUT -p tcp --dport 25 -j ACCEPT

iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

service iptables save

service iptables restart

测试发邮件:

mail -s "hosts" xxxx@qq.com < /etc/hosts

利用ffmpeg将MP4文件切成ts和m3u8(苹果官方推荐ffmpeg脚本)

1、将MP4转成m3u8

ffmpeg -i test.mp4 -codec copy -bsf h264_mp4toannexb test.ts

2、将ts转成m3u8

网上很多垃圾文章推荐segmenter工具,但用的时候,3.5G的ts文件丢了一半的数据,于是想到了ffmpeg转。

在国外网站找到命令,一句话搞定,没报半句错:

ffmpeg -i 12生肖.ts -c copy -map 0 -f segment -segment_list playlist.m3u8 -segment_time 10 output%03d.ts

顺便共享给各位国内的同仁,免得深受其苦。毕竟,大家都说HLS代表future,rtsp已经是过去式了。

苹果官方推荐ffmpeg脚本

#!/bin/sh

BR=800k

ffmpeg -i $1 -f mpegts -acodec libmp3lame -ar 48000 -ab 64k -s 320×240 -vcodec libx264 -b $BR -flags +loop -cmp +chroma -partitions +parti4x4+partp8x8+partb8x8 -subq 5 -trellis 1 -refs 1 -coder0 -me_range 16 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -bt 200k -maxrate $BR -bufsize $BR-rc_eq ‘blurCplx^(1-qComp)’ -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -level 30 -aspect 320:240 -g 30-async 2 sample_$BR_pre.ts

segmenter sample_$BR_pre.ts 10 sample_$BR stream-$BR.m3u8 http://www.ioncannon.net/

rm -f sample_$BR_pre.ts

可以节约CDN带宽流量的流媒体P2P(Flash/Android/IOS)

很多朋友关心SRS是否有计划支持RTMFP,是否计划支持P2P,这篇文章详细介绍了SRS和P2P的关系。

Summary

我们所指的P2P,并非传统客户端P2P的方式,譬如ed2k那种协议。我们特指三种P2P:

  1. FlashP2P:Adobe开发的P2P,Flash播放器之间可以互相P2P,分享视频。

  2. AndroidP2P:特指Android的App的P2P方式,Android上HTML5不可能做P2P。

  3. IOSP2P:特指IOS的App的P2P方式,IOS上HTML5不可能做P2P。

这三种P2P都有几个共同点:

  1. 只讨论流媒体范畴的P2P,普通文件和数据的P2P不考虑。

  2. 流媒体传输使用通用协议,譬如flv、mp4或hls,配合CDN完成P2P原始资源的传输,而并非所有的数据都是P2P网络用私有协议传输。

  3. 尽量避免安装额外插件。譬如FlashP2P就在flash播放器上跑(别纠结flash本身就是个插件),只需要集成AS的SDK,不需要额外安装ActiveX浏览器插件。而Android和IOS的P2P,需要集成P2P系统的SDK,只需要安装商家的App,而不需要再安装专门用来做P2P的App。

综上所述,我们可以将Flash/Android/IOS
P2P,简称为P2P。下面讲P2P一种可能的结构,以及SRS和P2P的关系。

Structure

一个P2P系统,可以包含下面几个结构:

  1. 客户端SDK:P2P系统必须提供客户端SDK,集成在播放器或者App中。譬如FlashP2P提供的是AS的库,Android提供的是java的库,IOS提供oc的库。

  2. API调度集群:P2P系统必须支持API调度,弥补DNS的不足,以及提供P2P系统需要的额外数据。API调度就是SDK交互的第一个后端,完成认证、其他服务器资源的分配、流信息、实时调度。

  3. RTMFP集群:或者称为基础协议集群,由API调度返回给SDK可用的服务器,客户端使用RTMFP服务器完成NAT打洞,以及必要的数据传输。

  4. Tracker集群:或者称为伙伴发现协议集群,由API调度返回给SDK可用的服务器,客户端向Tracker请求可用的伙伴节点。

  5. Pingback集群:或者称为实时数据集群,由API调度返回给SDK可用的服务器,并提供给API调度集群调度的实时数据依据,SDK向Pingback集群汇报实时数据。

  6. 流媒体源站集群:或者称为流媒体源,主要负责流媒体数据的生成,和CDN对接,由API调度返回给SDK可用的CDN边缘地址。

可以在SRS基础上完成的结构是:流媒体源站集群、RTMFP集群。其他大多是HTTP协议,主要是P2P系统内部的算法和逻辑处理,适合使用Python或者GO实现。

另外,Pingback集群需要提供10秒级别的系统数据,使用GO或者Spark都可以,数据量小时用GO实现也可以,数据量很大时可以用Spark。

下面详细分析SRS在WebP2P中的位置和状态。

SRS for P2P

回过头来说,SRS现在已经支持P2P中的流媒体源站集群和RTMFP集群了吗?SRS支持了流媒体源站集群,但是RTMFP集群不支持。

所谓SRS支持了流媒体源站集群,指的是SRS能输出一种HLS,能符合一种P2P系统的要求。这种P2P系统就是观止创想的P2P系统,具体参考BravoP2P。也就是说,若使用SRS作为您的流媒体服务器,是可以直接对接到观止的P2P系统的,可以给现有的HLS流加上P2P功能。

SRS为何不支持RTMFP集群呢,有几个原因:

  1. RTMFP目前不开源。

  2. RTMFP和SRS的差异太大,就算支持了RTMFP集群,还只是P2P系统的六分之一,没法用起来。

  3. SRS的目标是提供通用方案,SRS3的Dragon技术,SRS4对接Spark,目前还没有支持P2P系统的计划,P2P系统里面很多是私有方案。

因此,在现在的路线图,例如SRS3(预计2016年发布)和SRS4(预计2017年发布)的路线图中,都没有RTMFP的影子。

也就是说,在P2P系统中,SRS只计划支持流媒体源站功能。下面分开看看各种P2P系统。

FlashP2P

FlashP2P是由Adobe研发的P2P协议,包括握手、NAT打洞、数据传输,Adobe收购了一家做P2P的公司,将这个RTMFP协议集成到了Flash中。

FlashP2P在PC上的很成熟了,稳定性也可以达到商用的要求。从2013年开始,支持FlashP2P的公司也开始粗现,现在除了我们观止创想,还有云某动、快播解散后的一个团队等等。

AndroidP2P

Android手机、盒子和Pad上面支持P2P,这个目前还在发展中。

IOSP2P

IOS手机和Pad上面支持P2P,这个难度比AndroidP2P还大,目前没有消息。

Challenge

上面讲了各种P2P的情况,P2P的挑战有以下几点:

  1. 转换思维对接CDN:CDN最惧怕的就是P2P公司,不是要分他流量那么简单,而是对接起来灰常痛苦。据说有的FlashP2P系统,得在CDN每个边缘节点部署服务器,因为流媒体切片不通用,这不是要CDN的命么?因此首先最大的挑战就是转换为互联网思维,尽量使用通用方案,让CDN爽了P2P系统才能爽。

  2. 保证流畅度:传统P2P可以暂停缓冲个几个小时,而WebP2P直播正在进行时,缓冲个几次用户就刷新页面,这个P2P节点就相当于牺牲了。因此保证流畅度才能保证分享率,如何保证流畅度呢,这个就各显神通了。

  3. 实时调度:P2P的变化非常快,有的用户刷新页面啦,有的系统拖动啦,有的还暂停,有的就喜欢乱点。因此整个P2P的节点信息都是变化很快的,这对实时分析系统有非常大的挑战。

  4. 负载均衡和热备:P2P的集群也需要负载均衡,譬如RTMFP协议就支持Redirect方式,可以实现负载均衡和热备。传统P2P系统挂掉后节点就没法看视频,而一个P2P系统挂掉后依然能看,因为有CDN在那里呢,但是带宽就开始飙升了。而P2P系统的恢复需要较长时间,因此必须使用热备,在出现问题时切换到正常的系统。

这些挑战都是我们曾经遇到的,还包括没有遇到的。

移动直播技术秒开优化经验(含PPT)

徐立,七牛创始合伙人兼产品副总裁,负责七牛直播云的整体研发,是国内 Go / Docker / Container 技术早期布道者,Go / Containers / Distributed Systems 技术的忠实爱好者和实践者。曾合著国内第一本 Go 语言图书《Go 语言编程》,翻译《Go 语言程序设计》。

现今移动直播技术上的挑战要远远难于传统设备或电脑直播,其完整的处理环节包括但不限于:音视频采集、美颜/滤镜/特效处理、编码、封包、推流、转码、分发、解码/渲染/播放等。

直播常见的问题包括

  • 主播在不稳定的网络环境下如何稳定推流?

  • 偏远地区的观众如何高清流畅观看直播?

  • 直播卡顿时如何智能切换线路?

  • 如何精确度量直播质量指标并实时调整?

  • 移动设备上不同的芯片平台如何高性能编码和渲染视频?

  • 美颜等滤镜特效处理怎么做?

  • 如何实现播放秒开?

  • 如何保障直播持续播放流畅不卡顿?

本次分享将为大家揭开移动直播核心技术的神秘面纱。

视频、直播等基础知识

什么是视频?

首先我们需要理解一个最基本的概念:视频。从感性的角度来看,视频就是一部充满趣味的影片,可以是电影,可以是短片,是一连贯的视觉冲击力表现丰富的画面和音频。但从理性的角度来看,视频是一种有结构的数据,用工程的语言解释,我们可以把视频剖析成如下结构:

内容元素 ( Content )

  • 图像 ( Image )

  • 音频 ( Audio )

  • 元信息 ( Metadata ) 

编码格式 ( Codec )

  • Video : H.264,H.265, …

  • Audio : AAC, HE-AAC, …

容器封装 (Container)

  • MP4,MOV,FLV,RM,RMVB,AVI,…

任何一个视频 Video 文件,从结构上讲,都是这样一种组成方式:

  • 由图像和音频构成最基本的内容元素;

  • 图像经过视频编码压缩格式处理(通常是 H.264);

  • 音频经过音频编码压缩格式处理(例如 AAC);

  • 注明相应的元信息(Metadata);

最后经过一遍容器(Container)封装打包(例如 MP4),构成一个完整的视频文件。

如果觉得难以理解,可以想象成一瓶番茄酱。最外层的瓶子好比这个容器封装(Container),瓶子上注明的原材料和加工厂地等信息好比元信息(Metadata),瓶盖打开(解封装)后,番茄酱本身好比经过压缩处理过后的编码内容,番茄和调料加工成番茄酱的过程就好比编码(Codec),而原材料番茄和调料则好比最原本的内容元素(Content)。

视频的实时传输

简而言之,理性的认知视频的结构后,有助于我们理解视频直播。如果视频是一种“有结构的数据”,那么视频直播无疑是实时传输这种“有结构的数据”(视频)的方式。

那么一个显而易见的问题是:如何实时(Real-Time)传输这种“有结构的数据”(视频)呢?

这里边一个悖论是:一个经过容器(Container)封装后的视频,一定是不可变的 ( Immutable ) 视频文件,不可变的 ( Immutable ) 的视频文件已经是一个生产结果,根据“相对论”,而这个生产结果显然不可能精确到实时的程度,它已经是一段时空的记忆。

因此视频直播,一定是一个 “边生产,边传输,边消费”的过程。这意味着,我们需要更近一步了解视频从原始的内容元素 ( 图像和音频 ) 到成品 ( 视频文件 ) 之前的中间过程 ( 编码 )。

视频编码压缩

不妨让我们来深入浅出理解视频编码压缩技术。

为了便于视频内容的存储和传输,通常需要减少视频内容的体积,也就是需要将原始的内容元素(图像和音频)经过压缩,压缩算法也简称编码格式。例如视频里边的原始图像数据会采用 H.264 编码格式进行压缩,音频采样数据会采用 AAC 编码格式进行压缩。

视频内容经过编码压缩后,确实有利于存储和传输; 不过当要观看播放时,相应地也需要解码过程。因此编码和解码之间,显然需要约定一种编码器和解码器都可以理解的约定。就视频图像编码和解码而言,这种约定很简单:

编码器将多张图像进行编码后生产成一段一段的 GOP ( Group of Pictures ) , 解码器在播放时则是读取一段一段的 GOP 进行解码后读取画面再渲染显示。

GOP ( Group of Pictures ) 是一组连续的画面,由一张 I 帧和数张 B / P 帧组成,是视频图像编码器和解码器存取的基本单位,它的排列顺序将会一直重复到影像结束。

I 帧是内部编码帧(也称为关键帧),P 帧是前向预测帧(前向参考帧),B 帧是双向内插帧(双向参考帧)。简单地讲,I 帧是一个完整的画面,而 P 帧和 B 帧记录的是相对于 I 帧的变化。

如果没有 I 帧,P 帧和 B 帧就无法解码。

小结一下,一个视频 ( Video ) ,其图像部分的数据是一组 GOP 的集合, 而单个 GOP 则是一组 I / P / B 帧图像的集合。

在这样的一种几何关系中,Video 好比一个 “物体”,GOP 好比 “分子”,I / P / B 帧的图像则好比 “原子”。

想象一下,如果我们把传输一个 “物体”,改成传输一个一个的 “原子”,将最小颗粒以光速传送,那么以人的生物肉眼来感知,将是一种怎样的体验?

什么是视频直播?

不难脑洞大开一下,直播就是这样的一种体验。视频直播技术,就是将视频内容的最小颗粒 ( I / P / B 帧,…),基于时间序列,以光速进行传送的一种技术。

简而言之,直播就是将每一帧数据 ( Video / Audio / Data Frame ),打上时序标签 ( Timestamp ) 后进行流式传输的过程。发送端源源不断的采集音视频数据,经过编码、封包、推流,再经过中继分发网络进行扩散传播,播放端再源源不断地下载数据并按时序进行解码播放。如此就实现了 “边生产、边传输、边消费” 的直播过程。

理解以上两个关于 视频 和 直播 两个基础概念后,接下来我们就可以一窥直播的业务逻辑了。

直播的业务逻辑

如下是一个最精简的一对多直播业务模型,以及各个层级之间的协议。

各协议差异对比如下

以上就是关于直播技术的一些基础概念。下面我们进一步了解下影响人们视觉体验的直播性能指标。

影响视觉体验的直播性能指标

直播第一个性能指标是延迟,延迟是数据从信息源发送到目的地所需的时间。

根据爱因斯坦的狭义相对论,光速是所有能量、物质和信息运动所能达到的最高速度,这个结论给传播速度设定了上限。因此,即便我们肉眼感觉到的实时,实际上也是有一定的延迟。

由于 RTMP/HLS 是基于 TCP 之上的应用层协议,TCP 三次握手,四次挥手,慢启动过程中的每一次往返来回,都会加上一次往返耗时 ( RTT ),这些交互过程都会增加延迟。

其次根据 TCP 丢包重传特性,网络抖动可能导致丢包重传,也会间接导致延迟加大。

一个完整的直播过程,包括但不限于以下环节:采集、处理、编码、封包、推流、传输、转码、分发、拉流、解码、播放。从推流到播放,再经过中间转发环节,延迟越低,则用户体验越好。

第二个直播性能指标卡顿,是指视频播放过程中出现画面滞帧,让人们明显感觉到“卡”。单位时间内的播放卡顿次数统计称之为卡顿率

造成卡顿的因素有可能是推流端发送数据中断,也有可能是公网传输拥塞或网络抖动异常,也有可能是终端设备的解码性能太差。卡顿频次越少或没有,则说明用户体验越好。

第三个直播性能指标首屏耗时,指第一次点击播放后,肉眼看到画面所等待的时间。技术上指播放器解码第一帧渲染显示画面所花的耗时。通常说的 “秒开”,指点击播放后,一秒内即可看到播放画面。首屏打开越快,说明用户体验越好。

如上三个直播性能指标,分别对应一个低延迟、高清流畅、极速秒开 的用户体验诉求。了解这三个性能指标,对优化移动直播 APP 的用户体验至关重要。

那么移动直播场景下具体而言有哪些常见的坑呢?

根据实践总结下来的经验,移动平台上视频直播的坑主要可以总结为两方面:设备差异,以及网络环境这些场景下带来的技术考验。

移动直播场景的坑与规避措施

不同芯片平台上的编码差异

iOS 平台上无论硬编还是软编,由于是 Apple 一家公司出厂,几乎不存在因为芯片平台不同而导致的编码差异。

然而,在 Android 平台上,Android Framework SDK 提供的 MediaCodec 编码器,在不同的芯片平台上,差异表现很大, 不同的厂家使用不同的芯片,而不同的芯片平台上 Android MediaCodec 表现略有差异,通常实现全平台兼容的成本不低。

另外就是 Android MediaCodec 硬编层面的 H.264 编码画质参数是固定的 baseline,所以画质通常也一般。因此,在 Android 平台下,推荐是用软编,好处是画质可调控,兼容性也更好

低端设备如何上高性能地采集和编码?

例如 Camera 采集输出的可能是图片,一张图的体积并不会小,如果采集的频次很高,编码的帧率很高,每张图都经过编码器,那么编码器又可能会出现过载。

这个时候,可以考虑在编码前,不影响画质的前提下(前面我们讲过帧率的微观意义),进行选择性丢帧,以此降低编码环节的功耗开销。

弱网下如何保障高清流畅推流

移动网络下,通常容易遇到网络不稳定,连接被重置,断线重连,一方面频繁重连,建立连接需要开销。另一方面尤其是发生 GPRS / 2G / 3G / 4G 切换时,带宽可能出现瓶颈。当带宽不够,帧率较高/码率较高的内容较难发送出去,这个时候就需要可变码率支持。

即在推流端,可检测网络状态和简单测速,动态来切换码率,以保障网络切换时的推流流畅。

其次编码、封包、推流 这一部分的逻辑也可以做微调,可以尝试选择性丢帧,比如优先丢视频参考帧(不丢 I 帧和音频帧 ),这样也可以减少要传输的数据内容,但同时又达到了不影响画质和版视听流畅的目的。

需要区分直播流的状态和业务状态

直播是媒体流、APP 的交互是 API 信令流,两者的状态不能混为一谈。尤其是不能基于 APP 的交互的 API 状态来判断直播流的状态。

以上是移动直播场景下常见的几个坑和规避措施。

移动直播场景其他优化措施

一、怎么优化打开速度,达到传说中的 “秒开”?

大家可能会看到,市面上某些手机直播 APP 的打开速度非常快,一点就开。而某些手机直播 APP,点击播放后要等好几秒以后才能播放。是什么原因导致如此的天壤之别呢?

大部分播放器都是拿到一个完成的 GOP 后才能解码播放,基于 FFmpeg 移植的播放器甚至需要等待音画时间戳同步后才能播放(如果一个直播里边没有音频只有视频相当于要等待音频超时后才能播放画面)。

“秒开”可以从以下几个方面考虑:

1. 改写播放器逻辑让播放器拿到第一个关键帧后就给予显示。

GOP 的第一帧通常都是关键帧,由于加载的数据较少,可以达到 “首帧秒开”。

如果直播服务器支持 GOP 缓存,意味着播放器在和服务器建立连接后可立即拿到数据,从而省却跨地域和跨运营商的回源传输时间。

GOP 体现了关键帧的周期,也就是两个关键帧之间的距离,即一个帧组的最大帧数。假设一个视频的恒定帧率是 24fps(即1秒24帧图像),关键帧周期为 2s,那么一个 GOP 就是 48 张图像。一般而言,每一秒视频至少需要使用一个关键帧。

增加关键帧个数可改善画质(GOP 通常为 FPS 的倍数),但是同时增加了带宽和网络负载。这意味着,客户端播放器下载一个 GOP,毕竟该 GOP 存在一定的数据体积,如果播放端网络不佳,有可能不是能够快速在秒级以内下载完该 GOP,进而影响观感体验。

如果不能更改播放器行为逻辑为首帧秒开,直播服务器也可以做一些取巧处理,比如从缓存 GOP 改成缓存双关键帧(减少图像数量),这样可以极大程度地减少播放器加载 GOP 要传输的内容体积。

2. 在 APP 业务逻辑层面方面优化。

比如提前做好 DNS 解析(省却几十毫秒),和提前做好测速选线(择取最优线路)。经过这样的预处理后,在点击播放按钮时,将极大提高下载性能。

一方面,可以围绕传输层面做性能优化;另一方面,可以围绕客户播放行为做业务逻辑优化。两者可以有效的互为补充,作为秒开的优化空间。

二、美颜等滤镜如何处理?

在手机直播场景下,这就是一个刚需。没有美颜功能的手机直播 APP,主播基本不爱用。可以在采集画面后,将数据送给编码器之前,将数据源回调给滤镜处理程序,原始数据经过滤镜处理完后,再送回给编码器进行编码即可。

除了移动端可以做体验优化之外,直播流媒体服务端架构也可以降低延迟。例如收流服务器主动推送 GOP 至边缘节点,边缘节点缓存 GOP,播放端则可以快速加载,减少回源延迟。

其次,可以贴近终端就近处理和分发

三、如何保障直播持续播放流畅不卡顿?

“秒开”解决的是直播首次加载的播放体验,如何保障直播持续播放过程中的画面和声音视听流畅呢?因为,一个直播毕竟不是一个 HTTP 一样的一次性请求,而是一个 Socket 层面的长连接维持,直到直到主播主动终止推流。

上述我们讲过卡顿的定义:即播放时画面滞帧,触发了人们的视觉感受。在不考虑终端设备性能差异的情况下,针对网络传输层面的原因,我们看看如何保障一个持续的直播不卡顿。

这其实是一个直播过程中传输网络不可靠时的容错问题。例如,播放端临时断网了,但又快速恢复了,针对这种场景,播放端如果不做容错处理,很难不出现黑屏或是重新加载播放的现象。

为了容忍这种网络错误,并达到让终端用户无感知,客户端播放器可以考虑构建一个FIFO(先进先出)的缓冲队列,解码器从播放缓存队列读取数据,缓存队列从直播服务器源源不断的下载数据。通常,缓存队列的容量是以时间为单位(比如3s),在播放端网络不可靠时,客户端缓存区可以起到“断网无感”的过渡作用。

显然,这只是一个“缓兵之计”,如果直播服务器边缘节点出现故障,而此时客户端播放器又是长连接,在无法收到对端的连接断开信号,客户端的缓冲区容量再大也不管用了,这个时候就需要结合客户端业务逻辑来做调度。

重要的是客户端结合服务端,可以做精准调度。在初始化直播推流之前,例如基于 IP 地理位置和运营商的精确调度,分配线路质量最优的边缘接入节点。在直播推流的过程中,可以实时监测帧率反馈等质量数据,基于直播流的质量动态调整线路。

Q&A 

1. 关键帧设置频率一般是多少?有没有根据接入动态设置?过长首屏秒会很难做到。

徐立:关键帧间隔越长,也就是 GOP 越长,理论上画面越高清。但是生成 HLS 直播时,最小切割粒度也是一个 GOP,所以针对交互直播,通常不建议 GOP 设置太长。直播一般 2 个关键帧间隔即可。比如帧率是 24fps, 那么 2 个关键帧的间隔就是 48fps ,这个 GOP 就是2s。

2. 七牛这个直播是用的网宿加速?有遇到什么坑没?

徐立:七牛在直播方面主要是自建节点,也支持融合众多第三方 CDN 服务商,多样化的线路组合为客户提供更优质的服务。在和第三方 CDN 合作的过程中遇到的问题等有机会再做更细粒度的交流和分享。

3. RTMP 直播流除了优化线路外,还有什么加速手段吗?

徐立:物理上优化线路,逻辑上优化策略,比如选择性丢帧,不影响编码画质的前提下减轻传输体积。

4. OBS 推流,播放端 HLS 出现视/音频不同步是哪个环节的问题?怎么优化?

徐立:有可能是采集端的问题,如果是采集端编码环节就出现音画不同步,可以在收流服务器上做音画时间戳同步,这样是全局的校对。如果是播放端解码性能问题,那么需要调节播放逻辑,比如保证音画时间戳强一致性的前提下,选择性丢一部帧。

5. PPT 前几页中一个概念好像错了,I 帧不是关键帧,IDR 帧才是。IDR 帧是 I 帧,但是 I 帧不一定是 IDR 帧。只有 IDR 帧才是可重入的。

徐立:中文都把 I 帧翻译成关键帧了,不过既然提到了 IDR 帧,可以展开说明一下。所有的 IDR 帧都是 I 帧,但是并不是所有 I 帧都是 IDR 帧,IDR 帧是 I 帧的子集。I 帧严格定义是帧内编码帧,由于是一个全帧压缩编码帧,通常用 I 帧表示 “关键帧”。IDR 是基于 I 帧的一个 “扩展”,带了控制逻辑,IDR 图像都是 I 帧图像,当解码器解码到 IDR 图像时,会立即将参考帧队列清空,将已解码的数据全部输出或抛弃。重新查找参数集,开始一个新的序列。这样如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR 图像之后的图像永远不会使用 IDR 之前的图像的数据来解码。

6. 有没有调研过 nginx rtmp module,为什么没有用,对它有什么评价?

徐立:有调研过,nginx_rtmp_module 是单进程多线程,非 go 这种轻量级线程/协程用并发自然语义的方式编写流业务。nginx 原本的代码量较大(约 16 万行,但和直播业务相关的功能并不是很多)。且主要靠写 nginx.conf 做配置租户,通常单租户可以,但业务可扩展性方面不是很灵活,可满足基本需求,不满足高级功能。

7. 用到了那些开源软件?编码用的是 x264 吗?直播服务器你们自己开发还是开源的?

徐立:直播服务器用 go 开发的,移动端编码优先硬编,软编用 x264

8. 请教一下用 OBS 推流到 nginx_rtmp_module 的时候是已经做了视频压缩了还是需要基于 OBS 再开发?

徐立:OBS 把编码压缩都做了,不需要再开发。

9. 视频直播想在 HLS 流中无缝插入一段广告的 ts 文件,有问题想请教一下:1、这段 ts 的分辨率是否一定要和之前的视频流一致?2、pts 时间戳是否要和上一个 ts 递增?

徐立:1、可以不一致。这种情况两段视频完全是独立状态,可以没有任何关系,只需要插入 discontinue 标记,播放器在识别到这个标记之后重置解码器参数就可以无缝播放,画面会很平滑的切换。2、不需要递增。举个例子,视频 A 正在直播,播放到 pts 在 5s 的时候,插入一个视频 B,需要先插入一个 discontinue,再插入 B,等 B 播放完之后,再插入一个 discontinue,再插入 A,这个时候 A 的 pts 可以和之前递增,也可以按照中间插入的 B 的时长做偏移,一般做点播和时移的时候 pts 会连续递增,直播的话会算上 B 的时长。

PPT 下载地址

全民大直播,流媒体选择Nginx是福还是祸?

CDN,视频云,已经“僧多粥少”

视频直播的持续升温,无意间也让带宽生意的争夺变得异常残酷。一时间,各种云计算、CDN、视频云提供商都在视频尤其是直播上投入重兵,揭竿而起的新生起义军们也正马不停蹄的赶往这方战场,各种号称可以在IaaS、PaaS、SaaS不同层面提供平台级、接口级以及产品级服务的花式作战口号此起彼伏,让人眼花缭乱,“僧多粥少”可能成为了当前支撑视频技术解决方案市场最恰当的提法。如此局面之下,视频云和CDN们,技术上到底是在竞争什么?作为视频平台和即将要进入视频领域的运营者,在技术平台的选型和搭建上又如何才能避免掉入大坑?

一个播放器的背后

谁都知道视频直播最重要的是流畅和高清,但这光鲜亮丽的背后是技术和成本的双高门槛,是诸多技术环节艰难积累和苦逼的人肉运维。主播发起一个简单的直播,主干流程就历经了采集、编码、推流、转码、分发、拉流、解码和播放这么多环节,还要求在数秒内完成,除此之外直播还有如录制、流控、安全、审核等等诸多复杂功能需求。

再如下图,仅一个屌丝观众从播放器看这个主播,就可能出现如此多不可知情形发生。这个屌丝的接入网络怎么样?使用的系统环境又怎么样?一个观众尚且如此,要保障百万千万级别流畅的观看,难度可想而知。

高清流畅到底靠的是什么

也许对于部分视频运营商和新进入者来说,直播推流端和播放器端依然觉得头大,但整体来说,除移动端外,PC端推流和播放技术已经比较成熟。难,主要难在传输和分发!正常情况下,只要推流端网络状况良好,传输和分发决定着直播是否能够流畅。

传输和分发,涉及到了视频最核心技术、巨额服务器和带宽成本以及国内网络环境极度错综复杂也因为如此,视频平台基本上都将传输和分发环节交由专业的第三方视频云服务商或CDN服务商来完成。我们从网络传输的七层中拿出与视频传输分发相关的四层,如下图:

L2资源层:对视频云和CDN来说,资源的确存在差别,但在其可承受范围内,可以视为差别不大;

L4传输层:传输层可针对不同业务场景,比如针对超低延迟可以基于UDP做私有协议等。本文侧重阐述视频流畅的保障,不同应用场景的支持后续文章将专门介绍;

L3网络层:视频云和CDN公司在该层实现各运营商网间打通、多层Cache系统设计以及用户就近调度。该层的设计及优化对访问质量极为重要,随着CDN技术的日益成熟,虽然各家可能存在架构区别,但基本都能保障网络路由正常运转;

L7应用层:抛开细枝末节,视频流的主线还是输入、传输与输出,承担这些工作的就是视频平台最核心组件流媒体服务器,这就是视频直播分发最本质的特点,需要专门的流媒体服务器来分发,所有视频云和CDN,都需要在中心层和边缘层部署流媒体Server。

 

通过以上逐层分析可知,当资源和网络层面相差不大的情况下,流媒体Server的性能决定了视频流分发的效果和质量,故流媒体Server才是视频云和CDN技术竞争的至高点。



市面主要的流媒体服务器对比

目前市面上主流的流媒体服务器,有以Adobe FMS、Real Helix、Wowza为代表的第一代产品,它们的特点是单进程多线程。基于Linux2.7 epoll技术,出现了以多进程单线程为特点的第二代流媒体服务器,NginxRTMP、Crtmpd为其优秀的代表,另外还有基于JAVA的流媒体祖先Red5等。

观止云开源流媒体服务器SRS(Simple RTMP Server),凭借其功能强大、轻量易用、特别适合互动直播等诸多特点备受海内外视频从业者的青睐。蓝汛Chiancache曾用SRS承载其直播边缘分发业务,高升CDN基于SRS搭建其流媒体基础平台,其它还有赛维安讯、VeryCDN、VeryCloud、云博视等也将SRS应用到了自身的业务当中。各家视频云、云计算平台在源站的对接上也非常注重对SRS的支持。SRS作为纯国产的开源Server,在中国流媒体业界实属难能可贵。

观止云源站集群BMS(Bravo Media Server)是SRS的商业版,BMS在SRS基础上增强了11项大功能,新增了9个大功能

增项的11项大功能:



新增的9项大功能:





流媒体Server的话说来也不短,上述列举的目前市面上主流流媒体服务器中,有名副其实的先烈RED5,有生不逢时的CRTMPD,都未大规模商用就不过于讨论了。其中应用最为广泛莫属nginx-rtmp,以下是nginx-rtmp几个盛行于世的重要因素:

  • 2012年CDN业务开始极增长,随之直播需求也多了起来,彼时业界都还没有一套公认的特别满意的流媒体服务器;

  • Nginx是HTTP领域绝对的霸主,大家(尤其是CDN运维)对Nginx熟悉程度很高,便于上手维护;

  • 基于Nginx,直播点播使用一套服务器,这也极具诱惑力,一套管理起来总比多套要简单;

  • CDN是靠运维的行当,运维的信心都是长年运出来的,Nginx在图文上那么优秀,Nginx RTMP也差不了。



nginx-rtmp确实生来就自带光环外,性能也的确是高,比Crtmpd还要高。然而,时过境迁,随着互动直播、移动直播的强势兴起的大直播时代,选择nginx-rtmp到底是福还是祸?

下面小编将从协议支持、体系架构、核心功能支持、配置运维、性能、服务器日志、数据这七大维度将目前市面主流的流媒体Server做一个横向对比,供视频从业者根据自身业务场景特性择优选用。



1
网络协议对比

BMS支持HDS、DASH、RTMPE/S/T等协议的分发,这将支持更多业务应用场景,FLASH P2P的支持能够显著降低网络带宽成本。



2
体系架构对比

架构方面,较之于nginx-rtmp的16万行代码,SRS仅用了6.5万行代码就实现了比nginx-rtmp 多了230%的功能nginx-rtmp注释率为3%,而SRS是23.7%。由此可见SRS在体系架构上的轻,Simple。

观止云BMS在SRS的基础上新增了多进程支持、源站集群、动态配置、可追溯日志等方面能力。源站集群子系统打通了跨网跨地区的源站分布式部署难题;动态配置子系统从业务系统读取配置,依据更新机制动态更新配置,保证直播业务配置变化时依然不中断;端到端的可追溯日志及监控排错子系统将直播故障定位时间缩短到了分钟级别。



3
核心功能对比

核心功能方面,BMS支持了当期互动直播、移动直播急需的大规模直播流实时转码、大规模录制、秒级低延迟、HLS+、并发回源等其它所有流媒体系统不具备的功能。HLS+基于每个播放请求实现了流媒体的“虚拟连接 ”(UUID标识),在减小回源量、排错、防盗链、移动Web端低延迟等方面具有诸多优势。并发回源能够解决回源网络状况差、跨国传输丢包严重等方面能够显著提升回源质量。



4
配置运维对比

以下仅是流媒体众多配置之中几个常用例子,运维日常工作中,需要操作的配置数量更多。

(1)vhost配置

FMS

拷贝默认vhost目录:sudo cp -r conf/_defaultRoot_/_defaultVHost_ conf/_defaultRoot_/bravo.sina.com



nginx-rtmp

不支持



SRS/BMS

动态获取配置文件:vhost bravo.sina.com { }

结论:BMS动态获取配置最简单

(2)app配置

 FMS

拷贝默认app目录:cp applications/live applications/mylive -r



nginx-rtmp

修改配置文件,增加如下内容:application live {  live on; }



SRS/BMS

无需配置

结论:BMS无需配置,最简单 

(3)http配置

在输出为hls、http-flv等基于http协议的直播流时,需要配置http服务

FMS

配置FMS内置的Apache服务器文件:Apache2.2/conf/httpd.conf

再修改如下字段:

<Location /hds-live>

    HttpStreamingEnabled true

    HttpStreamingLiveEventPath “../applications” 

    HttpStreamingContentPath “../applications” 

    HttpStreamingF4MMaxAge 2

    HttpStreamingBootstrapMaxAge 2

    HttpStreamingFragMaxAge -1

    Options -Indexes FollowSymLinks

</Location



nginx-rtmp

nginx本身就是一个http服务器,

修改其配置文件:

conf/nginx.conf

设置端口和根目录:

http {

    include       mime.types;

    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    server {

        listen       80;

        server_name  localhost;

        location /dash {

            root /tmp;

            add_header Cache-Control no-cache;

        }

    }

}



SRS/BMS

修改其配置文件:

conf/http.hls.conf

设置端口和根目录:

http_stream {

    enabled         on;

    listen          8080;

    dir             ./objs/nginx/html;

}

结论:nginx-rtmp需指定与app对应的ts文件存放目录,SRS/BMS会自动生成,更简单。

(4)推流、播放URL配置

RTMP直播时,各大服务器推流、播流URL均为:

rtmp://server_ip_or_dns/app/stream



用作HLS直播时,

FMS 

推流域名:

rtmp://fms-ip-or-dns/app/stream?adbe-live-event=liveevent

播流域名:

http://fms-ip-or-dns/hds-live/app/_definst_/liveevent/stream.f4m



nginx-rtmp

推流域名:

rtmp://server_ip_or_dns/app/stream

播流域名:

http://server_ip_or_dns/app/stream.m3u8



SRS/BMS

同nginx-rtmp

结论:nginx-rtmp、SRS/BMS均简单,FMS较复杂。



5
性能

先说结论:

SRS单进程能支持9000并发,nginx-rtmp单进程最多支持3000个,单进程的性能SRS是nginx-rtmp的三倍。单进程性能SRS > nginx-rtmp > crtmpd > wowza > fms > RED5

 

再例举SRS性能如此高的几个原因:

1. st-load,这个是SRS能做到高性能的最重要的原因,一个st-load可以模拟2000+的客户端,如果没有st-load,如何知道系统的性能瓶颈在哪里?总不能打开3000个flash页面播放rtmp流吧?开启3000个ffmpeg来抓流?高性能不是想象和猜测出来的,而是反复测试、调试和改进出来的。

2. gperf/gprof性能,编译SRS时,就可以打开gcp或者gprof的性能分析选项,非常方便的拿到数据。缩短了改进和优化开发周期。

3. 引用计数的msgs避免内存拷贝。

4. 使用writev发送chunked包,避免消息到chunked包的内存拷贝。

5. mw(merged-write)技术,即一次发送多个消息。

6. 减少timeout recv,每个连接都是一个st-thread在服务。

7. fast buffer和cache。

8. vector还是list?vector!vector比list高10%性能。



6
服务器日志

日志是定位故障的唯一途径,定位故障才能快速排错。可以这么说,对于直播,10分钟的排错,谁都会觉得长。然而,当前的视频云或CDN,谁又能做到10分钟呢?

来看看日志吧。

FMS的日志是这样的,恕我愚钝,你能看得出什么信息么?

2015-03-24 12:23:58 3409 (s)2641173 Accepted a connection from IP:192.168.1.141, referrer:http://www.ossrs.net/players/srs_player/release/srs_player.swf?_version=1.23,pageurl: http://www.ossrs.net/players/srs_player.html?vhost=dev&stream=livestream&server=dev&port=1935-

702111234525315439     3130         3448         normal      livestream         –        –         rtmp://192.168.1.185:1935/live/livestream     rtmp://192.168.1.185:1935/live/livestream        –        flv     –        –        0       –        0       0         –        –    http://www.ossrs.net/players/srs_player.html?vhost=dev&stream=livestream&server=dev&port=1935    -1      -1.000000         

crtmpd的日志详细,但我又愚钝,若是上千人在线,你又能看出什么有用的东西么?

/home/winlin/tools/crtmpserver.20130514.794/sources/thelib/src/netio/epoll/iohandlermanager.cpp:120Handlers count changed: 15->16 IOHT_TCP_CARRIER

/home/winlin/tools/crtmpserver.20130514.794/sources/thelib/src/netio/epoll/tcpacceptor.cpp:185Client connected: 192.168.1.141:54823 -> 192.168.1.173:1935

/home/winlin/tools/crtmpserver.20130514.794/sources/applications/appselector/src/rtmpappprotocolhandler.cpp:83Selected application: flvplayback (live)

/home/winlin/tools/crtmpserver.20130514.794/sources/thelib/src/application/baseclientapplication.cpp:246Protocol CTCP(17) <-> TCP(18) <-> [IR(19)] unregistered fromapplication: appselector

/home/winlin/tools/crtmpserver.20130514.794/sources/thelib/src/application/baseclientapplication.cpp:257Stream NR(5) with name “ registered to application `flvplayback` from protocolIR(19)

/home/winlin/tools/crtmpserver.20130514.794/sources/thelib/src/application/baseclientapplication.cpp:268Stream NR(5) with name “ unregistered from application `flvplayback` fromprotocol IR(19)

/home/winlin/tools/crtmpserver.20130514.794/sources/thelib/src/application/baseclientapplication.cpp:257Stream NR(6) with name “ registered to application `flvplayback` from protocolIR(19)

/home/winlin/tools/crtmpserver.20130514.794/sources/thelib/src/protocols/rtmp/basertmpappprotocolhandler.cpp:1043Play request for stream name `livestream`. Start: -2000; length: -1000

/home/winlin/tools/crtmpserver.20130514.794/sources/thelib/src/application/baseclientapplication.cpp:268Stream NR(6) with name “ unregistered from application `flvplayback` fromprotocol IR(19)   

到了nginx-rtmp,日志总算有进步了,能按照连接区分了。只是可惜,nginx日志也就只能知道这个连接而已,这个连接在CDN多层网络中的路径,这个连接本身的网络状况等依然不知。

2015/03/2411:42:01 [info] 7992#0: *3 client connected ‘192.168.1.141’

2015/03/2411:42:01 [info] 7992#0: *3 connect: app=’live’ args=” flashver=’MAC17,0,0,134’swf_url=’http://www.ossrs.net/players/srs_player/release/srs_player.swf?_version=1.23’tc_url=’rtmp://192.168.1.173:1935/live’page_url=’http://www.ossrs.net/players/srs_player.html?vhost=dev&stream=livestream&server=dev&port=1935’acodecs=3575 vcodecs=252 object_encoding=3, client: 192.168.1.141, server:0.0.0.0:1935

2015/03/2411:42:01 [info] 7992#0: *3 createStream, client: 192.168.1.141, server:0.0.0.0:1935

2015/03/2411:42:01 [info] 7992#0: *3 play: name=’livestream’ args=” start=0 duration=0reset=0 silent=0, client: 192.168.1.141, server: 0.0.0.0:1935



在SRS,尤其是BMS身上,终于有了流媒体可追溯日志,能从播放连接追到对应的推流连接,打印出连并接的摘要信息,这也是观止云能将故障定位时间控制到分钟级别的原因。之前小编专门介绍过观止云可追溯日志,有兴趣可参考《可追溯日志:视频云时代的新运维大胸器》,此处简单看看可追溯日志运行方式:

播放流:rtmp://dev:1935/live/livestream 客户端显示ID能看到SrsIp,即服务器IP为192.168.1.107,由此知道是哪个边缘节点在提供该流的服务。SrsPid为12665,SrsId为114,所以去这个服务器上grep关键字“[12665] [114]”。







连续grep追踪,最终发现这是source_id=149 的编码器推上来的流ID。再去查149的日志,整个流的日志将快速呈现在眼前。Encoder => Origin => Edge => Player,流在观止云整体分发过程中的日志能够几分钟内找到。



7
数据

小编尚且不完全知晓,数据对于一个视频运营平台来说价值到底有多大。小编知道的,至少对于视频直播,依托于越实时的数据,越能够快速定位、解决部分用户故障问题;保障不同付费等级、不同终端、不同区域、不同内容等的观看体验;进行CDN计费数据对账;精准广告等等。

所以观止云BMS还是尽能力的提供了一些数据,而且是几乎实时的数据,上几张图:



可以看到实时在线观看人数,以及区域、运营商分布,观看流畅度,实时带宽负载等

 



可定位到某个具体用户,监控用户的终端环境(操作系统、浏览器、播放器版本),观看体验。

 

其它系统呢,对不起,小编没有见到过。

视频云直播中的关键帧(I帧)技术探秘

一.关键帧的痛点

在视频领域,电影、电视、数字视频等可视为随时间连续变换的许多张画面,而帧则指这些画面当中的每一张。如果把这些帧转换成图片文件,并原封不动的根据一个指定的格式连续摆放的话,就得到了一个视频文件。当然,这个文件有点类似电影的胶片。

不过如果按照如此的方式存储视频的话,文件势必会变得很大,而且其间有很多重复的数据。所以需要专门的算法对视频文件进行编码。对于视频的编码格式来讲,常见的就是H264。

一旦视频进行编码之后,得到的文件可以看做是连续的一组帧的集合,而这一组帧中的每一个都是有自己的类型的。帧的类型分为以下3种:

* Inter Frame(I帧)

* P-Frame(P帧)

* B-Frame(B帧)

其中只有I帧中的数据是可以自描述的,也就是说当我们获得I帧的数据之后,就可以直接解码出当前帧的图像,对于B帧和P帧来说需要找到对应的一个或者多个参考帧才能解码出来,见图一所示;



图一.jpg

因此对于非I帧来说想要进行解码就需要多个参考帧进行计算,并得出最终的结果。由此引出了Group of Picture的概念。

对于P帧和B帧来说,他们所包含的内容可以理解为针对其参考帧的一个patch,也就是一个变化量,所以他们不用包含整个图像的信息,只要描述好与参考帧之间的变化关系即可。所以在字节大小方面,P帧和B帧要远远小于I帧的。这也是视频压缩能节省空间的一个原因所在。

 

二.GoP性能调优分析



Group of Picture(以下简称GoP)顾名思义就是有一组帧组成的一个序列。Wikipedia上给出的一个图简单的解释了GoP是怎么回事(见图二):

图二.jpg

GoP由I帧开始,后面跟随者一组B帧和P帧,直到下一个I帧之前的帧为一个GoP。了解了GoP之后,就会发现播放器只有在拿到某个GoP中的I帧之后才能播放视频。对于GoP来说,编码器都是可以进行设置的,像OBS,ffmpeg等程序可以通过对应的设置和参数对视频的GoP进行设置。

那么引出了一个问题:GoP到底应该设置多大?那么GoP的大小到底有什么影响呢?

  1. GoP设置比较大时:

好处:由于B帧和P帧的字节大小会比I帧小很多,所以GoP越长,所包含的B帧和P帧越多,响应的压缩比也会更高,或者说同样的码率下,视频会更清晰一些。

坏处:对于视频直播来说,播放器连接到服务器的时间是不固定的,当播放器在GoP中间连接服务器,并获取了中间的B帧和P帧,这时播放器是无法对这些帧进行解码的,需要进行丢弃。所以会导致客户端的首屏播放时间变长(客户端需要等待一段时间才能看到图像)。

  2.  如果 GoP设置比较小时:

好处:由于GoP设置小可以降低I帧间隔时间,对于直播来说可以实现秒开的功能。

坏处:由于GoP时间比较短,会导致I帧的比例增高,压缩比降低。同样码率情况下视频的质量会有所下降。

 

三 .为什么我的HLS视频加载会慢?



HLS (HTTP Live Streaming),Apple的动态码率自适应技术。主要用于PC和Apple终端的音视频服务。HLS格式的视频分为两个部分的。首先,HLS会根据指定的切片时间和实际的GoP大小对视频进行切割,并生成.ts文件。其次,HLS会生成一个.m3u8文件来保存这些ts文件的索引。

 

HLS协议可以用来做点播,也可以用来做直播。HLS直播是对直播流实时进行格式转换,并切片出.ts文件,同时更新.m3u8文件。客户端通过间歇获取新的.m3u8文件来获取新的.ts文件的索引。HLS点播是通过预先转码好的视频进行切片,并生成一个完整的.m3u8文件,客户端通过获取.m3u8文件来得到视频的时长和各个.ts切片文件的索引。

对于HLS格式的直播来说,.m3u8文件会在生成完一个.ts文件之后才生成。所以对于HLS直播来说,刚开始推流时,到第一个.ts文件生成完毕之前是无法打开的。同理,HLS的延迟也是跟.ts文件切片时间相关的。也就是说HLS的ts文件切片时间为1秒的话,HLS直播的延迟最小为1秒。

当然对于播放端来讲,下载.m3u8文件,然后下载第一个.ts文件也是需要花费一点时间的,那么这个时间也会加在延迟中。

对于.ts文件的切割来讲,并不是告诉直播服务器指定1秒切一个.ts文件他就能保证1秒切一个.ts文件的。.ts文件的切割还是要根据直播视频的实际GoP大小来进行切割的。之前已经讲过,任何一个视频流在播放端需要能获取到完整的GoP才能播放,所以一个.ts文件所实际包含的时间是GoP的整数倍。

例如:当视频的GoP设置为1秒,.ts切片时间为2秒时,实际的.ts文件切片所包含的视频为2秒。当视频的GoP设置为5秒,.ts切片时间为2妙时,实际的.ts文件切片所包含的视频为5秒。

从上面两个例子不难发现,视频流的实际GoP对HLS切片的时间影响非常大。如果视频流的GoP大小设置不合适的话,那么HLS的切片时间就会变长,同时也会增加HLS的延迟。这个特性对于HLS直播来讲简直就是延迟杀手。如果推流上来的GoP为10秒的话,不要说切出来一个.ts文件就要10秒,同时下载一个.ts文件所花费的时间也会大大增加。

另外如果是HLS点播的话,流的GoP设置过大也会影响点播视频的加载时间。一般的一个720P的视频,如果切片时间为2秒的话,单个.ts文件也就是在百K字节上下。对于现有的网速来讲,下载这么一个.ts文件很快。但如果源视频的GoP很大,会导致第一个.ts文件所包含的视频时常变长(比如10秒),同时导致.ts文件的大小膨胀到接近1M字节上下。想想看,如果第一个.ts文件是1M字节的话,播放器下载这个.ts文件的时间会是多久。

 

四.为什么我的RTMP直播首屏渲染速度很慢?



RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。该协议基于TCP,是一个协议族,包括RTMP基本协议及RTMPT/RTMPS/RTMPE等多种变种。RTMP是一种设计用来进行实时数据通信的网络协议,主要用来在Flash/AIR平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。

对于RTMP协议的直播视频来说,它并不像HLS协议需要切.ts文件,它只是把视频流实时地进行转发即可。RTMP协议本身也会抽象出一个Packet的概念来封装H264编码中的帧,也就是一个Packet会包含1到多个帧,播放器以Packet为单位来进行解码。那么RTMP的问题在于客户端连接的时间点是否合适。

例如一个RTMP直播流的GoP设置为2秒,如果客户端接入时间刚好是第4秒,那么客户端会获取一个包含I帧的Packet,由于I帧是自描述的,所以客户端可以直接解码出该帧的画面并显示出来。但是当客户端的接入之间为第5秒,那么他会获得一个包含B帧或者P帧的Packet,由于客户端拿到的数据是一个不完整的GoP,所以客户端只好抛弃当前获取的Packet中视频的数据,而且只有当获取到包含下一个GoP的I帧的Packet时才能解码出图像。因此客户端会等待1秒才能播放出画面。

由此我们可以得出一个结论:GoP的大小会影响RTMP播放端的首帧加载时间。也就是说首帧加载时间最久为一个GoP的时间。当然,如何客户端运气够好的话,可以瞬间播放。

为了优化首帧加载时间,我们可以在流媒体服务器端增加一个缓存,把上一个GoP缓存在内存中。如果客户端接入的话,我们首先放出来的是上一个GoP。这样客户端接到的数据永远是一I帧开头的数据。

不过这种方案对于延迟要求比较高的场景下就不适合。毕竟GoP的缓存会增加一个延迟,具体延迟的时间也是跟GoP大小相关的。如果要实时性,那么GoP缓存并不能很好的解决问题,只能通过减少关键帧间隔的方式来进行调优了。



五.结论



在视频直播和点播盛行的年代,对于GoP大小的取舍还是需要看具体应用场景。对于直播来讲,对延迟要求敏感的应用来说,1~2秒的GoP大小还是比较合适的,至于GoP缓存来讲,还是不用为好。如果是对延迟要求不敏感,对首屏播放时间很敏感的应用,GoP还是1~2秒最为合适,GoP缓存应该是必备的。另外直播使用HLS的话,延迟是绝对PK不过RTMP的。

对于点播的应用来说,视频加载速度是个硬指标,如果不是HLS格式的话,GoP大小适当选大一点可以降低视频文件大小,提高视频打开速度。HLS格式的话,还是推荐在2秒左右,否则很影响视频打开速度的。

其实视频直播技术的挑战很多,这次分享的只是其中一小部分,也是迅达云SpeedyCloud研发团队的经验总结,希望能够和大家多交流,一起为技术社区发展做些有益的事情。



六.Q&A



问题1:求推荐视频直播的知识资源,关于rmtp、h264编码的更多知识。大量视频文件存储服务器技术方案有哪些?

这方面的内容,可以通过翻阅wikipedia,Google来获取想要的知识。另外看一些开源项目的文档也会有所帮助。比如Nginx-RTMP-Module,Simple-rtmp-server,ffmpeg

 

问题2:视频直播这块有没有开源项目可以来练手的?  

上面说到了Nginx-rtmp,SRS,FFMPEG

 

问题3:nginx 的rtmp模块,可以配置gop缓存时间吗?

nginx-rtmp是没有GoP Cache的。Simple-RTMP-Server是有的。

 

问题4:求现在创业公司如何快速搭建自己的直播平台,是自研还是使用第三方平台,有哪些第三方平台可以推荐?

搭建直播平台的话,涉及的东西会很多,一般都是自研一部分,外包一部分。

 

问题5:老师说到B侦和P侦需要参考侦才能解码,但在GoP的那张图中没有看到参考侦呀?

最前面和最后的两个橘黄色的是I帧。另外参考不一定是I帧,前后的B帧和P帧都可以做参考

 

问题6:ffmepg里,哪个选项是配置 x264的 gop的?

-g参数可以设置gop长度,单位是帧。

 

问题7:就rtmpserver来说,哪些server 使用者更多些?

这个看应用场景了,如果是自己用,Nginx-rtmp比较容易一些,如果是做CDN的话,simple-rtmp-server是个比较好的选择。

Nginx下实现pathinfo及ThinkPHP的URL Rewrite模式支持

打开Nginx的配置文件 /usr/local/nginx/conf/nginx.conf 一般是在这个路径,根据你的安装路径可能有所变化。如果你配置了vhost,而且只需要你这一个vhost支持pathinfo的话,可以直接打开你的vhost的配置文件。找到类似如下代码(不同版本的nginx可能稍有不同,但是相差不会很远): 


C代码  收藏代码

  1. location ~ .*.(php|php5)?$  
  2. {  
  3.         #原有代码  
  4. }  




C代码  收藏代码

  1. #去掉$是为了不匹配行末,即可以匹配.php/,以实现pathinfo  
  2. #如果你不需要用到php5后缀,也可以将其去掉  
  3. location ~ .php  
  4. {  
  5.     #原有代码  
  6.   
  7.     #定义变量 $path_info ,用于存放pathinfo信息  
  8.     set $path_info “”;  
  9.     #定义变量 $real_script_name,用于存放真实地址  
  10.     set $real_script_name $fastcgi_script_name;  
  11.     #如果地址与引号内的正则表达式匹配  
  12.     if ($fastcgi_script_name ~ “^(.+?\.php)(/.+)$”) {  
  13.         #将文件地址赋值给变量 $real_script_name  
  14.         set $real_script_name $1;  
  15.         #将文件地址后的参数赋值给变量 $path_info  
  16.         set $path_info $2;  
  17.     }  
  18.     #配置fastcgi的一些参数  
  19.     fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;  
  20.     fastcgi_param SCRIPT_NAME $real_script_name;  
  21.     fastcgi_param PATH_INFO $path_info;  
  22. }  





这样,nginx服务器就可以支持pathinfo了。但是如果要支持ThinkPHP的URL_MODE设置为2的模式,还需要配置rewrite规则。找到access_log语句,在其上方加上以下语句: 


C代码  收藏代码

  1. #如果请求既不是一个文件,也不是一个目录,则执行一下重写规则  
  2. if (!-e $request_filename)  
  3. {  
  4.     #地址作为将参数rewrite到index.php上。  
  5.     rewrite ^/(.*)$ /index.php/$1;  
  6.     #若是子目录则使用下面这句,将subdir改成目录名称即可。  
  7.     #rewrite ^/subdir/(.*)$ /subdir/index.php/$1;  
  8. }  





最后,保存配置文件,重启nginx服务,把ThinkPHP的URL_MODEL设置为2,访问下你的页面,如果能正常访问,恭喜你pathinfo配置成功了。 



贴上配置文件: 

C代码  收藏代码

  1. server {  
  2.         listen       80;  
  3.         server_name  localhost;  
  4.     index index.html index.htm index.php;  
  5.     root /home/www;  
  6.           
  7.     # think项目 增加过滤功能,支持Rewrite  
  8.     location /think {  
  9.         # ThinkPHP Rewrite, 除以上指定的静态资源外,其它的请求才有必要进行判断  
  10.         if (!-e $request_filename){  
  11.             rewrite ^/think/(.*)$ /think/index.php/$1 last;  
  12.         }  
  13.     }  
  14.   
  15.         #去掉$是为了不匹配行末,即可以匹配.php/,以实现pathinfo  
  16.         #如果你不需要用到php5后缀,也可以将其去掉  
  17.     location ~ .*\.(php|php5)  
  18.     {  
  19.         #fastcgi_pass  unix:/tmp/php-cgi.sock;  
  20.         fastcgi_pass  127.0.0.1:9000;  
  21.         fastcgi_index index.php;  
  22.         include fastcgi.conf;  
  23.   
  24.         #定义变量 $path_info ,用于存放pathinfo信息  
  25.                 set $path_info “”;  
  26.                 #定义变量 $real_script_name,用于存放真实地址  
  27.                 set $real_script_name $fastcgi_script_name;  
  28.                 #如果地址与引号内的正则表达式匹配  
  29.                 if ($fastcgi_script_name ~ “^(.+?\.php)(/.+)$”) {  
  30.                         #将文件地址赋值给变量 $real_script_name  
  31.                         set $real_script_name $1;  
  32.                         #将文件地址后的参数赋值给变量 $path_info  
  33.                         set $path_info $2;  
  34.                 }  
  35.                 #配置fastcgi的一些参数  
  36.                 fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;  
  37.                 fastcgi_param SCRIPT_NAME $real_script_name;  
  38.                 fastcgi_param PATH_INFO $path_info;  
  39.   
  40.     }  
  41.     location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$  
  42.     {  
  43.         expires 30d;  
  44.     }  
  45.     location ~ .*\.(js|css)?$  
  46.     {  
  47.         expires 1h;  
  48.     }  
  49. }  
  50.   
  51. # 日志打印出来,查看请求数据  
  52. log_format  _access  ‘$remote_addr – $remote_user [$time_local] “$request” ‘  
  53.               ‘$status $body_bytes_sent “$http_referer” ‘  
  54.               ‘”$http_user_agent” “$http_x_forwarded_for” “$request_filename”‘;  
  55. access_log  /home/log/nginx/access/php.log _access;  

Nginx服务器涉及到的安全配置

0x00 测试环境


操作系统:CentOS6.5
Web服务器:Nginx1.4.6
Php版本:Php5.4.26

0x01 Nginx介绍


nginx本身不能处理PHP,它只是个web服务器,当接收到请求后,如果是php请求,则发给php解释器处理,并把结果返回给客户端。nginx一般是把请求发fastcgi管理进程处理,fastcgi管理进程选择cgi子进程处理结果并返回被nginx。

nginx涉及到两个账户,一个是nginx的运行账户,一个是php-fpm的运行账户。如果访问的是一个静态文件,则只需要nginx的运行账户对文件具有读取权限;而如果访问的是一个php文件,则首先需要nginx的运行账户对文件有读取权限,读取到文件后发现是一个php文件,则转发给php-fpm,此时则需要php-fpm账户对文件具有读取权限。

0x02 研究发现的结论


1. linux下,要读取一个文件,首先需要具有对文件所在文件夹的执行权限,然后需要对文件的读取权限。 
2. php文件的执行不需要文件的执行权限,只需要nginx和php-fpm运行账户的读取权限。 
3. 上传木马后,能不能列出一个文件夹的内容,跟php-fpm的运行账户对文件夹的读取权限有关。 
4. 木马执行命令的权限跟c的账户权限有关。 
5. 如果木马要执行命令,需要php-fpm的账户对相应的sh有执行权限。 
6. 要读取一个文件夹内的文件,是不需要对文件夹有读取权限的,只需要对文件夹有执行权限。 

0x03 Nginx服务器涉及到的安全配置


1. Nginx.conf的配置 
2. php-fpm.conf的配置 
3. nginx和php-fpm的运行账户对磁盘的权限配置 
4. Php.ini的配置 

0x04 常见需要配置的操作方法


1. 禁止一个目录的访问

示例:禁止访问path目录

location ^~ /path { 
deny all; 
} 

可以把path换成实际需要的目录,目录path后是否带有”/”,带“/”会禁止访问该目录和该目录下所有文件。不带”/”的情况就有些复杂了,只要目录开头匹配上那个关键字就会禁止;注意要放在fastcgi配置之前。

2. 禁止php文件的访问及执行

示例:去掉单个目录的PHP执行权限

location ~ /attachments/.*\.(php|php5)?$ { 
deny all; 
} 

示例:去掉多个目录的PHP执行权限

location ~  
/(attachments|upload)/.*\.(php|php5)?$ { 
deny all; 
} 

3. 禁止IP的访问

示例:禁止IP段的写法:

deny 10.0.0.0/24; 

示例:只允许某个IP或某个IP段用户访问,其它的用户全都禁止

allow  
x.x.x.x;  
allow 10.0.0.0/24;  
deny all; 

0x05 需要解决的常见问题


1. 让木马上传后不能执行

针对上传目录,在nginx配置文件中加入配置,使此目录无法解析php。

2. 让木马执行后看不到非网站目录文件

取消php-fpm运行账户对于其他目录的读取权限。

3. 木马执行后命令不能执行

取消php-fpm账户对于sh的执行权限。

4. 命令执行后权限不能过高

Php-fpm账户不要用root或者加入root组。

0x06 Nginx安全配置方案


1. 修改网站目录所有者为非php-fpm运行账户,此处修改所有者为root。

命令:

1
chown -R root:root html/

2014032713413545690.png

2. 修改nginx及php-fpm的运行账户及组为nobody

nginx.conf

2014032713415348030.png

Php-fpm.conf

2014032713421181020.png

3. 取消nobody对所有目录的的读取权限,然后添加对网站目录的读取权限

命令:

1
2
chmod o-r –R  /
chmod o+r –R html/

4. 取消nobody对于/bin/sh 的执行权限 

chmod 776 /bin/sh

5. 确认网站目录对于nobody的权限为可读可执行,对网站文件的权限为可读

6. 对于上传目录或者写入写文件的目录添加nobody的写入权限 

7. 配置nginx.conf 对于上传目录无php的执行权限

8. 配置nginx.conf禁止访问的文件夹,如后台,或者限制访问ip

9. 配置nginx.conf禁止访问的文件类型,如一些txt日志文件

CDN已成云商必争之地 看CDN年整体收入增速800%的阿里云如何放大招?

近日,阿里云PR一改往日规模会议的模式,落脚创业大街,在3W咖啡办起了CDN专项技术媒体分享会。据笔者观察,这是阿里云2016年PR思路的一大改观,与同为BAT级的腾讯云一同走起了小清新路线。从邀请函中明显看出,本场媒体沙龙的主题剑指传统CDN,“传统CDN会更好?”的疑问在本次技术分享中被揭晓。

传统CDN闷声挣大钱的时代已去

随着用户应用需求类型的改变,CDN作为网络的一部分,已经成为企业级市场追捧的焦点之一。因为通过CDN可以更多的把计算和存储推向边缘,实实在在的让用户感受到最真实的体验。所以,当云计算大规模落地,云化CDN越来越受到用户的青睐。

我们知道,传统CDN一直以来,盘踞在产业链条的一端,悄默声的挣去高额利润。但在阿里云首席科学家章文嵩的眼里,这种传统CDN闷声发大财的局面,很快或者已经被打破。“云化CDN会完全重新将CDN定义。”因为随着创新技术的发展,用户应用习惯的更改。更多的应用场景会更多的被推到边缘节点上,不光是云上的计算,也可能变成边缘计算。“CDN可以说是云的入口,对于任何的云厂商来说CDN都很重要,而且CDN一定是云计算厂商的必争之地。” 章文嵩表示。

为什么肯定?原因很简单,可以被主要归纳为两点。第一,云时代里,企业级需求在改变。用户不在单一将CDN当做一个固定资源来所取,而是在其中加入更多计算、存储、网络、安全等多方面需求,而传统CDN厂商并不具备这样的综合技术实力,所以云服务商就有了更大的机会;第二,池化资源是云计算的本质属性,CDN一旦池化,将会带来更多的规模与价格优势,而这也是传统CDN能力达不到的。据介绍,像云厂商或者国内的BAT级别的互联网公司,他们在基础设施规模的建设上都比传统的CDN厂商大得多。整体的规模效应会带来成本下降,从而会带来更低的价格。

CDN的C从内容(Content)到云(Cloud)的改变

“未来三五年,随着云厂商的进入,整个CDN产业的格局将会发生翻天覆地的变化。” 章文嵩预测。

CDN原来的单词是Content Delivery Network,侧重整体应用的内容分发。而随着云厂商在产业内的介入,大部分企业用户的应用构建、传输都将在云上进行,所以CDN的内涵也将逐步转变为Cloud Delivery Network,云上网络分发的印记将更加深入。

“随着用户需求的适时而变,CDN技术的发展将会融合更多的创新思维和前瞻性的理念进去,云厂商凭借自身的综合实力优势,发展会越来越快。未来两到三年,传统CDN服务商光环不在,有可能整个CDN的行业江湖就会易主。” 章文嵩表示。

对此,阿里云CDN事业部总监朱照远用实际案例带来例证。分享当天,他举了阿里云与芒果TV合作打造风靡全球“超级女声”APP的例子。芒果TV对阿里云提出了苛刻的CDN需求:两周打造一个原型出来!其中APP要实现包括视频的海选、直播与终极PK的功能,其中技术实现要能提供视频的转码、网络专用通道、数据库、消息中间件等等十几种的能力。“这对传统CDN是不可能做到的,因为他们解决不了用户的所有痛点,或需求的功能点。传统的CDN只能提供单一的CDN组件,但是客户需要的却是计算、存储、分发、大数据、安全等等系列解决方案。”

所以,传统CDN在面临复杂的业务场景以及复杂的客户需求时,会表现出能力不足,束手无策。在云时代里,单一的内容分发,已经不能满足用户的多样化需求,云化CDN将给用户带来全新选择。

CDN6.0版本有哪些绝杀技?

阿里云CDN发源于淘宝自有CDN,并于2014年3月开始正式对外为提供服务。在商业化服务2周年之际,阿里云发布了经过10年淘宝历练,5次更新迭代的CDN6.0版本。

据介绍,此次发布的极速CDN 6.0版本,除了首次在业界提出Cloud Delivery Network(云分发网络)理念外。还融合了云计算、大数据技术,涵盖视频和移动两个解决方案以及大数据分析、HTTPS加速等新功能,可以为客户提供一站式的云CDN解决方案。

具体说来,6.0版本针对视频类应用推出视频云解决方案,提供一站式视频点播、直播服务,集内容采集、上传加速、存储、码转/截图、鉴黄服务、CDN分发及播放器等功能于一体,极大地优化了客户体验。据测算,视频云端到端时延仅2秒,流畅度95%,在同等清晰度下,码率低30%,这意味着客户可以节省30%的成本。

另外,新版CDN还提供了HTTPS加密功能,可以让客户内容防劫持、防篡改、防窃密,保证通信的安全。

大数据工具也是6.0版本的重要特色。阿里云将打通CDN和“数加”平台的大数据计算产品MaxCompute,客户如果需要,可以一键上传自己的日志数据,使用强大的MaxCompute进行精细化分析数据,让数据产生商业价值。



▲阿里云CDN用户展示

据透漏,移动端的CDN整体解决方案也即将上线,移动加速可提升40%性能,并具备HTTPDNS功能,具有防域名劫持、调度精确、稳定可靠等特点。

2015年阿里云CDN客户数已突破10万,客户规模是传统CDN厂商客户之和的20倍,在整个CDN市场稳居行业第一。业务营收同比增长800%,增长率也比传统CDN厂商的增速快了约20倍。“这一年越来越多的大中型企业级CDN客户采用CDN和云计算证明了我们对于行业前景的判断。企业在使用公共云架构的同时选择了云CDN,其综合竞争优势显著,这与传统IT架构向云计算转变的历史趋势一致。” 朱照远表示。

▲国内500+节点,超过10T带宽,单节点40-300G,海外覆盖6大洲(图示是阿里云CDN有部署节点的国家或地区)

linux运维工程师的朋友,必须要掌握以下几个工具才行

本人是linux运维工程师,对这方面有点心得,现在我说说要掌握哪方面的工具吧

说到工具,在行外可以说是技能,在行内我们一般称为工具,就是运维必须要掌握的工具。

我就大概列出这几方面,这样入门就基本没问题了。

linux系统如果是学习可以选用redhat或centos,特别是centos在企业中用得最多,当然还会有其它版本的,但学习者还是以这2个版本学习就行,因为这两个版本都是兄弟,没区别的,有空可以再研究一下SUSE,有些公司也喜欢用,例如我公司 。。。。。

工具如下:





1、linux系统基础,这个不用说了,是基础中的基础,连这个都不会就别干了,参考书籍,可以看鸟哥linux基础篇,至少要掌握这书60%内容,没必须全部掌握,但基本命令总得会吧





2、网络服务,服务有很多种,每间公司都会用到不同的,但基础的服务肯定要掌握,如FTP, DNS,SAMBA, 邮件, 这几个大概学一下就行,LAMP和LNMP是必须要熟练,我所指的不是光光会搭建,而是要很熟悉里面的相当配置才行,因为公司最关键的绝对是WEB服务器,所以nginx和apache要熟悉,特别是nginx一定要很熟悉才行,至少有些公司还会用tomcat,这个也最好学一下。其实网络服务方面不用太担心,一般公司的环境都已经搭建好,就算有新服务器或让你整改,公司会有相应的文档让你参照来弄,不会让你乱来的,但至少相关的配置一定要学熟,而且肯定是编译安装多,那些模块要熟悉一下他的作用,特别是PHP那些模块。

这面2点只是基础,也是必要条件,不能说是工具,下以才是真正的要掌握的工具。





3、shell脚本和另一个脚本语言,shell是运维人员必须具备的,不懂这个连入职都不行,至少也要写出一些系统管理脚本,最简单也得写个监控CPU,内存比率的脚本吧,这是最最最基本了,别以为会写那些猜数字和计算什么数的,这些没什么作用,只作学习意义,写系统脚本才是最有意义,而另一个脚本语言是可选的,一般是3P,即python, perl和php,php就不需要考虑了,除非你要做开发,我个人建议学python会比较好,难实现自动化运维,perl是文本处理很强大,反正这两个学一个就行了。





4、sed和awk工具,必须要掌握,在掌握这两个工具同时,还要掌握正则表达式,这个就痛苦了,正则是最难学的表达式,但结合到sed和awk中会很强大,在处理文本内容和过滤WEB内容时十分有用,不过在学shell的同时一般会经常结合用到的,所以学第3点就会顺便学第4点。





5、文本处理命令,sort , tr , cut, paste, uniq, tee等,必学,也是结合第3点时一并学习的。





6、数据库,首选mysql,别问我为什么不学sqlserver和oracle,因为linux用得最多绝对是mysql,增删改查必学,特别要学熟查,其它方面可能不太需要,因为运维人员使用最多还是查,哪些优化和开发语句不会让你弄的。





7、防火墙,不学不行,防火墙也算是个难点,说难不难,说易不易,最重要弄懂规则,如果学过CCNA的朋友可能会比较好学,因为iptables也有NAT表,原理是一样的,而FILTER表用得最多,反正不学就肯定不合格。





8、监控工具,十分十分重要,我个人建议,最好学这3个,cacti,nagios,zibbix,企业用得最多应该是nagios和 zibbix,反正都学吧,但nagios会有点难,因为会涉及到用脚本写自动监控,那个地方很难。





9、集群和热备,这个很重要,肯定要懂的,但到了公司就不会让你去弄,因为新手基本不让你碰,集群工具有很多,最好学是LVS,这是必学,最好也学学nginx集群,反向代理,还有热备,这个就更多工具能实现了,像我公司是自己开发热备工具的,mysql热备也要学,就是主从复制,这个别告诉我容易,其实不容易的,要学懂整个流程一点也不容易,只照着做根本没意思。





10、数据备份,不学不行,工具有很多,但至少要把RAID的原理弄懂,特别是企业最常用的1+0或0+1,自己做实验也要弄出来,备份工具有很多,如tar, dump, rsync等,最好多了解一下。

算了,说到这10点已经够你受了,应该可以入门了,因为有些技术会比较难学,例如apache和nginx中还有些很重要的技术,如系统调优和服务优化,还有程序优化,这些在没接触工作前很难学习到的,所以先把这10点学了吧,估计要学熟至少3个月不止,就脚本那部分已经让你很吃力了,我建议是先学熟shell,等工作后再学另一门脚本语言,这样会比较好。




以上就是踏入linux运维工程师需要掌握的工具,其实还有很多工具要掌握的,但你在学习环境中是很难学到,最后我再提醒一下,这里所指的工具相当于技能,而不是像windows或ubuntu那些图形化工具,那些工具没用的,还有,学linux就别装图形界面,这样虚拟机就不用吃太多内存,而且绝对不建议在真机上装linux,根本达不到学习效果。

转载:http://bbs.51cto.com/thread-1087414-1.html