月度归档:2016年08月

移动直播技术秒开优化经验(含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;