现在的位置: 首页 > 综合 > 正文

Nginx搭建flv视频点播服务器

2014年09月05日 ⁄ 综合 ⁄ 共 10438字 ⁄ 字号 评论关闭

        前一段时间使用Nginx搭建的多媒体服务器只能在缓冲过的时间区域内拖放, 而不能拖放到未缓冲的地方. 这就带来了一个问题: 如果视频限速的速率很小, 那么客户端观看视频时肯定不流畅, 而且用户不能向前拖放, 用户体验很不好. 如果视频限速的速率很大或者不限速, 服务器是承受不了的, 特别是在某个热门视频高并发访问的情况下, 而且客户端浏览器也在快速的从服务器接收数据, 同样会造成客户端视频播放不流畅的问题, 对服务器的性能和网络带宽都是很大的挑战. 所以很有必要将实现视频服务器的点播功能, 这样既可以对视频进行限速,
避免大量不必要数据在网上的传送, 又可以改善用户体验.

        本文主要参考了 [1] 的实现, 期间会遇到各种意想不到问题, 然后从网上搜索到了解决方法. 本次搭建使用的Nginx版本是1.4.1, jwplayer的版本是6.6.

资料:

HTTP Live Streaming(缩写是 HLS)是一个由苹果公司提出的基于HTTP的流媒体 网络传输协议。
HLS只请求基本的HTTP报文,与实时传输协议(RTP)不同,HLS可以穿过任何允许HTTP数据通过的防火墙或者代理服务器。它也很容易使用内容分发网络来传输媒体流。

一 准备

搭建点播服务器需要如下几个模块:

  • nginx_mod_h264_streaming: 使nginx支持h264编码的视频
  • http_flv_module: 支持flv
  • http_mp4_module: 支持mp4
  • nginx-rtmp-module: 支持rtmp协议

其中http_flv_module和http_mp4_module两个模块是nginx自带的, 可以在编译的时候加上相应的选项.

nginx_mod_h264_streaming的下载地址: http://h264.code-shop.com/trac/wiki/Mod-H264-Streaming-Nginx-Version2

nginx-rtmp-module托管在GitHub上: https://github.com/arut/nginx-rtmp-module

注意在GitHub的"Build"部分有这样一句话:

Several versions of nginx (1.3.14 - 1.5.0) require http_ssl_module to be added as well:

1
./configure
--add-module=<path-to-nginx-rtmp-module> --with-http_ssl_module

会报错【ngx_http_streaming_module.c:158: 错误:‘ngx_http_request_t’ 没有名为 ‘zero_in_uri’ 的成员】之类的错误。这需要修改nginx_mod_h264_streaming-2.2.7的源代码:修改ngx_http_streaming_module.c,注释掉
if (r->zero_in_uri)
{
return NGX_DECLINED;
}
这一段。
之后make clean,之后重新configure,重新make,之后make install。

我用的nginx版本是1.4.1, 所以在configure的时候需要加上"--with-http_ssl_module"选项, 否则会出现错误:

1
2
3
4
5
src/http/ngx_http_request.c:
在函数‘ngx_http_set_virtual_server’中:
src/http/ngx_http_request.c:1992:9:
错误: 未知的类型名‘ngx_http_ssl_srv_conf_t’
src/http/ngx_http_request.c:1999:16:
错误: ‘ngx_http_ssl_module’未声明(在此函数内第一次使用)
src/http/ngx_http_request.c:1999:16:
附注: 每个未声明的标识符在其出现的函数内只报告一次
src/http/ngx_http_request.c:2001:17:
错误: 在非结构或联合中请求成员‘verify’

ssl的安装可以参考网上教程, 也可以参考 [1] . 注意Ubuntu环境下SSL库是libssl-dev:

sudo
apt-get install libssl-dev

 

关于ffmpeg及其依赖库的安装, 请参考我的上一篇文章: http://www.cnblogs.com/wanghetao/p/3386311.html, 在此不再赘述.

   

 二 安装Nginx及其相关模块

(Nginx使用的是Perl正则表达式, 所以需要首先安装PCRE, 读者能够从网上找到PCRE的安装过程, 在此不赘述. )

# mkdir nginx

# cd nginx

# cp ../nginx-1.4.1.tar.gz ../nginx_mod_h264_streaming-2.2.7.tar.gz ../nginx-rtmp-module-master.zip ./

#  tar -zxvf nginx-1.4.1.tar.gz                                   (同样地将其余两个包解压缩)

# cd nginx-1.4.1

# ./configure --prefix=/usr/local/nginx --add-module=../nginx_mod_h264_streaming-2.2.7 --add-module=../nginx-rtmp-module-master/ --with-http_flv_module --with-http_mp4_module --with-http_gzip_static_module --with-http_stub_status_module --with-cc-opt="-I/usr/local/ffmpeg2/include"
--with-ld-opt="-L/usr/local/ffmpeg2/lib"

若想安装nginx-upstream-jvm-route(忽略)

./configure --prefix=/usr/local/nginx --add-module=../nginx_mod_h264_streaming-2.2.7
 --add-module=../nginx-upstream-jvm-route-read-only  --with-http_stub_status_module  --add-module=../nginx-rtmp-module-master/ --with-debug --with-http_stub_status_module --with-http_ssl_module --with-http_flv_module --with-http_sub_module --with-http_realip_module

./configure --prefix=/usr/local/nginx --add-module=../nginx_mod_h264_streaming-2.2.7  --add-module=../nginx-rtmp-module-master/ --with-debug --with-http_stub_status_module --with-http_ssl_module --with-http_flv_module --with-http_sub_module --with-http_realip_module

# make

这个时候出现了错误:

1
2
3
4
5
6
7
8
9
10
../nginx_mod_h264_streaming-2.2.7/src/mp4_reader.c:
在函数‘esds_read’中:
../nginx_mod_h264_streaming-2.2.7/src/mp4_reader.c:377:16:
错误: 变量‘stream_priority’被设定但未被使用 [-Werror=unused-but-set-variable]
../nginx_mod_h264_streaming-2.2.7/src/mp4_reader.c:376:12:
错误: 变量‘stream_id’被设定但未被使用 [-Werror=unused-but-set-variable]
../nginx_mod_h264_streaming-2.2.7/src/mp4_reader.c:
在函数‘stsd_parse_vide’中:
../nginx_mod_h264_streaming-2.2.7/src/mp4_reader.c:529:22:
错误: 变量‘level_indication’被设定但未被使用 [-Werror=unused-but-set-variable]
../nginx_mod_h264_streaming-2.2.7/src/mp4_reader.c:528:22:
错误: 变量‘profile_compatibility’被设定但未被使用 [-Werror=unused-but-set-variable]
../nginx_mod_h264_streaming-2.2.7/src/mp4_reader.c:527:22:
错误: 变量‘profile_indication’被设定但未被使用 [-Werror=unused-but-set-variable]
../nginx_mod_h264_streaming-2.2.7/src/mp4_reader.c:526:22:
错误: 变量‘configuration_version’被设定但未被使用 [-Werror=unused-but-set-variable]
cc1:
all warnings being treated as errors
make[1]:
*** [objs/addon/src/mp4_reader.o] 错误 1

 # vim objs/Makefile (修改objs/Makefile文件, 去掉其中的"-Werror"), 然后就能够正常编译了.

# make

# make install

至此nginx安装成功了.

 

三 修改nginx配置文件nginx.conf, 配置虚拟主机

这一部分请参考 [1] , 其中比较传神的地方是限速功能的实现. 使用一个端口提供视频文件视频文件的访问以及并发访问量和限速功能(比如8081), 然后从另一个端口配置虚拟主机, 在网页中使用http协议对8081的视频文件进行访问.

NGINX启动时提示错误:

/usr/local/nginx/sbin/nginx -t
/usr/local/nginx/sbin/nginx: error while loading shared libraries: libpcre.so.1: cannot open shared object file: No such file or directory

ldd $(which /usr/local/nginx/sbin/nginx)

linux-vdso.so.1 => (0x00007fff48ff0000)
libcrypt.so.1 => /lib64/libcrypt.so.1 (0×0000003065800000)
libpcre.so.1 => not found
libssl.so.6 => /lib64/libssl.so.6 (0×0000003067000000)
libcrypto.so.6 => /lib64/libcrypto.so.6 (0×0000003066400000)
libdl.so.2 => /lib64/libdl.so.2 (0×0000003063000000)
libz.so.1 => /lib64/libz.so.1 (0x0000003063c00000)
libc.so.6 => /lib64/libc.so.6 (0x0000003062c00000)
libgssapi_krb5.so.2 => /usr/lib64/libgssapi_krb5.so.2 (0x0000003066c00000)
libkrb5.so.3 => /usr/lib64/libkrb5.so.3 (0x0000003069c00000)
libcom_err.so.2 => /lib64/libcom_err.so.2 (0×0000003068800000)
libk5crypto.so.3 => /usr/lib64/libk5crypto.so.3 (0×0000003069000000)
/lib64/ld-linux-x86-64.so.2 (0×0000003062800000)
libkrb5support.so.0 => /usr/lib64/libkrb5support.so.0 (0x000000306a800000)
libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x0000003067c00000)
libresolv.so.2 => /lib64/libresolv.so.2 (0×0000003068400000)
libselinux.so.1 => /lib64/libselinux.so.1 (0×0000003064400000)
libsepol.so.1 => /lib64/libsepol.so.1 (0×0000003064000000)

解决方法:

ln -s /usr/local/lib/libpcre.so.1 /lib64

32位系统则:

ln -s /usr/local/lib/libpcre.so.1 /lib

注:
/usr/local/lib/libpcre.so.1 为prce安装后的文件地址
低版本prce对应的libpcre.so.1 为libpcre.so.0

nginx的启动命令是:

/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf

-c制定配置文件的路径,不加-nginx会自动加载默认路径的配置文件。

 

以上是通用的启动命令

以下是转载的,对于有以下命令的nginx可以使用,没有以下命令的nginx,可以使用上面的方法

研究了一下nginx帮助后发现,有-s参数可对nginx服务进行管理:
# /usr/local/nginx/sbin/nginx -h
nginx version: nginx/0.7.63
Usage: nginx [-?hvVt] [-s signal] [-c filename] [-p prefix] [-g directives]

Options:
-?,-h : this help
-v : show version and exit
-V : show version and configure options then exit
-t : test configuration and exit
-s signal : send signal to a master process: stop, quit, reopen, reload 
-p prefix : set prefix path (default: /usr/local/nginx/)
-c filename : set configuration file (default: conf/nginx.conf)
-g directives : set global directives out of configuration file

于是我执行
# /usr/local/nginx/sbin/nginx -s  reload 
nginx已经重启成功

停止操作
停止操作是通过向nginx进程发送信号(什么是信号请参阅linux文 章)来进行的
步骤1:查询nginx主进程号
ps -ef | grep nginx
在进程列表里 面找master进程,它的编号就是主进程号了。
步骤2:发送信号

从容停止Nginx:
kill -QUIT 主进程号

快速停止Nginx:
kill -TERM 主进程号

强制停止Nginx:
pkill -9 nginx


另外, 若在nginx.conf配置了pid文件存放路径则该文件存放的就是Nginx主进程号,如果没指定则放在nginx的logs目录下。有了pid文 件,我们就不用先查询Nginx的主进程号,而直接向Nginx发送信号了,命令如下:
kill -信号类型 '/usr/nginx/logs/nginx.pid'

平滑重启

如果更改了配置就要重启Nginx,要先关闭Nginx再打开?不是的,可以向Nginx 发送信号,平滑重启。
平滑重启命令:
kill -HUP 住进称号或进程号文件路径

或者使用

/usr/nginx/sbin/nginx -s reload

注意,修改了配置文件后最好先检查一下修改过的配置文件是否正 确,以免重启后Nginx出现错误影响服务器稳定运行。判断Nginx配置是否正确命令如下:
nginx -t -c /usr/nginx/conf/nginx.conf

或者

/usr/nginx/sbin/nginx -t


平滑升级
如果服务器正在运行的Nginx要进行升级、添加或删除模块时,我们需 要停掉服务器并做相应修改,这样服务器就要在一段时间内停止服务,Nginx可以在不停机的情况下进行各种升级动作而不影响服务器运行。
步骤1:

如 果升级Nginx程序,先用新程序替换旧程序文件,编译安装的话新程序直接编译到Nginx安装目录中。
步 骤2:执行命令
kill -USR2 旧版程序的主进程号或进程文件名

此时旧的Nginx主进程将会把自己的进程文件改名为.oldbin,然后执行新版 Nginx。新旧Nginx会同市运行,共同处理请求。

这时要逐步停止旧版 Nginx,输入命令:

kill -WINCH 旧版主进程号

慢慢旧的工作进程就都会随着任务执行完毕而退出,新版的Nginx的工作进程会逐渐取代旧版 工作进程。


此 时,我们可以决定使用新版还是恢复到旧版。

不重载配置启动新/旧工作进程

kill -HUP 旧/新版主进程号

从容关闭旧/新进程

kill -QUIT 旧/新主进程号

如果此时报错,提示还有进程没有结束就用下面命令先关闭旧/新工作进程,再关闭主进程号:

kill -TERM 旧/新工作进程号


这样下来,如果要恢复到旧版本,只需要上面的几个步 骤都是操作新版主进程号,如果要用新版本就上面的几个步骤都操作旧版主进程号就行了。


上面就是Nginx的一些基本的操作,希望以后Nginx能有更好的方法来处理这些操作, 最好是Nginx的命令而不是向Nginx进程发送系统信号。

四 flv文件转码和元数据的注入

使用ffmpeg转码后的flv文件需要注入元数据(metadata)之后才能实现拖放, metadata中主要是关键帧. 我使用yamdi实现元数据的注入. yamdi为flv文件增加了很多metadata信息,比如创建者、是否有关键帧、是否有视频、是否有音频,视频高度和宽度等等。而yamdi加入的meta数据中,最有效的要数关键帧。被注入了关键帧的flv可以实现像土豆网、优酷网等大型视频网站一样的“拖进度”,提前拖到缓冲还未加载到的位置开始播放。另一个可用的工具是flvtool2,
网上一个文章提供了两者的用法和比较: http://blog.csdn.net/zhangxh1013/article/details/5896482.

yamdi的安装也很简单:

(1) 下载: http://yamdi.sourceforge.net/

(2) # tar -zxvf yamdi-1.9.tar.gz

(3) # cd yamdi-1.9

(4) # make & make install

元数据注入:

1
yamdi
-i input.flv -o output.flv

 

输出的文件使用ffmpeg查看, 可以看到这样的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Metadata:
   creator        
: m_8efd9a8eb9c1c7434e76fc0e27052f91_md Tudou, Inc.
   metadatacreator
: Tudou Metadata Injector
for FLV
- Version 1.0
   hasKeyframes   
:
true
   hasVideo       
:
true
   hasAudio       
:
true
   hasMetadata    
:
true
   canSeekToEnd   
:
false
   datasize       
: 14485828
   videosize      
: 13399710
   audiosize      
: 1041346
   lasttimestamp  
: 251
   lastkeyframetimestamp:
245
   lastkeyframelocation:
14458306
   encoder        
: Lavf55.12.100

 这说明元数据注入成功, 并且输出文件中含有关键帧.

当然, 在ffmpeg转码的同时也可以向flv文件中加入关键帧, 只需加入 "-keyint_min" 参数. ffmpeg文档中对此的解释是:

1
2
3
‘keyint_min
integer (encoding,video)’
 
    Set
minimum interval between IDR-frames.      (设定IDR帧之间的最小间隔)

 

但是用ffmpeg转码的方法添加元数据仍然不能对视频随意拖放, 用ffmpeg可以看到转码后的视频的元数据信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ffmpeg
-i output.flv
......
 Metadata:
    creator        
: m_8efd9a8eb9c1c7434e76fc0e27052f91_md Tudou, Inc.
    metadatacreator
: Tudou Metadata Injector
for FLV
- Version 1.0
    hasKeyframes   
:
true
    hasVideo       
:
true
    hasAudio       
:
true
    hasMetadata    
:
true
    canSeekToEnd   
:
false
    datasize       
: 14485828
    videosize      
: 13399710
    audiosize      
: 1041346
    lasttimestamp  
: 251
    lastkeyframetimestamp:
245
    lastkeyframelocation:
14458306
    encoder        
: Lavf55.12.100

 它与使用yamdi方法产生的结果的位于区别是"canSeekToEnd"为false, 为什么这样的flv视频不能拖放还有待于以后研究.

 

五 jwplayer播放器的设定

通过以上四个步骤, flv视频点播服务器已经基本搭建成功了. 下面要使用jwplayer播放视频, 检验以上的设置是否成功. 编写以下PHP文档并在浏览器中打开:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE
heml>
<html>
    <head>
        <script
type=
"text/javascript" src="jwplayer/jwplayer.js"></script>
        <meta
http-equiv=
"Content-Type" content="text/html;charset=utf-8" />
    </head>
     
    <body>       
        <div
id=
"myElement">Loading
the page...</div>
        <script
type=
"text/javascript">
            var file_name
=
"<?php
echo  $_GET['id']; ?>"
;
            jwplayer("myElement").setup({
                file:"http://localhost:82/" +
file_name,
                //
image: "data/myposter.jpg",
                title:
file_name,
            });
        </script>
    </body>
</html>

  

 浏览器中输入URL:

 满怀激动地心情, 结果还是失望了: 视频仍然不能拖放!!! 为什么会这样呢? 原因是jwplayer的设定不正确. 需要向jwplayer中添加SEEK功能, 告诉它要对文件进行seek, 这是通过"starttime"参数实现的. 可以参考: http://www.longtailvideo.com/support/jw-player/28855/pseudo-streaming-in-flash.
对于不同编码和不同视频格式的文件, "starttime"的值各不相同. 对于h264编码的flv格式文件, "starttiem"设为"start".

 

修改完成后, 在浏览器中再次输入URL, 尝试拖动进度条, 成功了!!! 真的很高兴..来个高清大图给乃们瞧瞧.

参考文献:

[1] http://5iqiong.blog.51cto.com/2999926/1132639

抱歉!评论已关闭.