nginx搭建rtmp服务器(原有nginx上添加模块方式)及让nginx支持HLS

 nginx  nginx搭建rtmp服务器(原有nginx上添加模块方式)及让nginx支持HLS已关闭评论
1月 122021
 

转自:https://www.cnblogs.com/HintLee/p/9499429.html

参考:   https://www.jianshu.com/p/089b70f57bca

前言

最近接手了一个跟视频监控相关的项目,用了近年来越来越流行的 Web 服务器 nginx 加上 nginx-rtmp-module 搭建 rtmp 服务器。使用了阿里云的服务器,系统 Ubuntu 16.04 。

步骤

更新源并安装 nginx 。

sudo apt-get update
sudo apt-get install nginx

一、 支持RTMP

输入 nginx -V 查看 nginx 版本,可以看到当前版本号是 1.10.3,且可以看到编译选项。所以下一步要做的是下载相同版本的 nginx 源码,使用相同的编译选项并添加 nginx-rtmp-module,替换原来的 nginx 。
下载 nginx 1.10.3 的源码和 nginx-rtmp-module 的源码。

wget https://nginx.org/download/nginx-1.10.3.tar.gz
tar zxvf nginx-1.10.3.tar.gz
git clone https://github.com/sergey-dryabzhinsky/nginx-rtmp-module.git
cp -r nginx-rtmp-module nginx-1.10.3

在第 3 步中可以得知安装的 nginx 的编译选项,所以套用这些编译选项,在上一步已经把 nginx-rtmp-module 复制到 nginx 源码目录,所以在结尾添加 –add-module=./nginx-rtmp-module 。在 nginx-1.10.3 目录执行以下命令:

./configure --with-cc-opt='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_v2_module --with-http_sub_module --with-http_xslt_module --with-stream --with-stream_ssl_module --with-mail --with-mail_ssl_module --with-threads --add-module=./nginx-rtmp-module

上一步执行后可能会提示以下几个错误,需安装相关软件包,然后再次执行步骤 5 的命令。

./configure: error: the HTTP rewrite module requires the PCRE library.
./configure: error: SSL modules require the OpenSSL library.
./configure: error: the HTTP XSLT module requires the libxml2/libxslt
./configure: error: the HTTP image filter module requires the GD library.
./configure: error: the GeoIP module requires the GeoIP library.
sudo apt-get install libpcre3 libpcre3-dev
sudo apt-get install openssl libssl-dev
sudo apt-get install libxml2 libxml2-dev libxslt-dev
sudo apt-get install libgd2-xpm-dev
sudo apt-get install libgeoip-dev

上一步执行完成后 make,等待 nginx 编译完成。编译过程可能会出现 error: macro “DATE” might prevent reproducible builds 错误,在 CFLAGS 中添加 -Wno-error=date-time 参数即可,也就是步骤5的命令改成

./configure --with-cc-opt='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -Wno-error=date-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_v2_module --with-http_sub_module --with-http_xslt_module --with-stream --with-stream_ssl_module --with-mail --with-mail_ssl_module --with-threads --add-module=./nginx-rtmp-module

编译完成后,在 objs 目录下会有 nginx 的可执行文件。首先停止 nginx 服务,替换掉 nginx 。

sudo service nginx stop
cd /usr/sbin
sudo mv nginx nginx.bak
sudo cp ~/nginx-1.10.3/objs/nginx ./

修改 /etc/nginx/nginx.conf,在结尾添加使其开启 nginx-rtmp-module 相关的功能。

rtmp {
        server {
                listen 1935;
                chunk_size 4000;
                application live {
                        live on;
                }
        }
}

执行 sudo service nginx restart 重启 nginx 服务,然后执行 netstat -a|grep 1935,可以看到 1935 端口处于 LISTEN 状态,即可向 nginx 推流。更多强大的功能可以查看 nginx-rtmp-module

 

二、 支持HLS

找到rtmp 修改下面这个地方

rtmp {
    server {
        listen 1935;
        application live {
            live on;
            record off;
        }
 
        # HLS
 
        # For HLS to work please create a directory in tmpfs (/tmp/hls here)
        # for the fragments. The directory contents is served via HTTP (see
        # http{} section in config)
        #
        # Incoming stream must be in H264/AAC. For iPhones use baseline H264
        # profile (see ffmpeg example).
        # This example creates RTMP stream from movie ready for HLS:
        #
        # ffmpeg -loglevel verbose -re -i movie.avi  -vcodec libx264
        #    -vprofile baseline -acodec libmp3lame -ar 44100 -ac 1
        #    -f flv rtmp://localhost:1935/hls/movie
        #
        # If you need to transcode live stream use 'exec' feature.
        #
        application hls {
            live on;
            hls on;
            hls_path /usr/local/var/www/hls;
        }
 
        # MPEG-DASH is similar to HLS
 
        application dash {
            live on;
            dash on;
            dash_path /tmp/dash;
        }
    }
 } 
保存配置文件,重新加载nginx配置

nginx -s reload

2.进行推流测试(使用ffmpeg,安装见最后)

ffmpeg -loglevel verbose -re -i /Data/Movies/Demo.mov  -vcodec libx264 -vprofile baseline -acodec libmp3lame -ar 44100 -ac 1 -f flv rtmp://localhost:1935/hls/movie

然后你就可以在这个目录
/usr/local/var/www/hls
看到生成一个个ts的文件,还会生成一个movie.m3u8的文件

在Safari里输入地址查看视频(需要等movie.m3u8文件生成后),也可以用iPad或者iPhone上的Safari来访问(其他设备记得需要把localhost改为nginx的ip地址)
http://localhost:8080/hls/movie.m3u8

附:
ffmpeg安装:

Ubuntu

Ubuntu 中安装比较简单,直接使用 apt 安装即可

1
$ sudo apt install -y ffmpeg

CentOS

CentOS 中则比较麻烦,需要使用源码安装

下载并解压

1
2
3
$ wget https://ffmpeg.org/releases/ffmpeg-4.1.tar.bz2
$ tar -xjvf ffmpeg-4.1.tar.bz2
$ cd ffmpeg-4.1

编译

1
2
$ ./configure
nasm/yasm not found or too old. Use --disable-x86asm for a crippled build.

可以使用 --disable-x86asm 参数略过该配置或者直接安装

1
$ sudo yum install -y yasm

也可以从官网下载源码安装

随后再次编译安装即可

1
2
$ ./configure
$ sudo make && sudo make install

CentOS6下基于Nginx搭建mp4/flv流媒体服务器(可随意拖动)并支持RTMP/HLS协议(含转码工具)

 centos, 视频  CentOS6下基于Nginx搭建mp4/flv流媒体服务器(可随意拖动)并支持RTMP/HLS协议(含转码工具)已关闭评论
12月 222015
 

记录下,以备不时之需。

转自:http://hdu104.com/294 

1.先添加几个RPM下载源

     1.1)安装RPMforge的CentOS6源
     [[email protected] ~]# wget -c http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm
     [[email protected] ~]# rpm –import http://apt.sw.be/RPM-GPG-KEY.dag.txt
     [[email protected] ~]# rpm -i rpmforge-release-0.5.3-1.el6.rf.*.rpm    

     1.2)安装epel源
     [[email protected] ~]# wget -c http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
     [[email protected] ~]# rpm -Uvf epel-release-6-8.noarch.rpm

     安装完成,查看一下源列表,看到rpmforge和epel字样即可
     [[email protected] ~]# ls -lh /etc/yum.repos.d

2.安装转码工具Mencoder及ffmpeg(约定:每个小点操作之前先回到用户主目录,即cd ~)

     2.1)安装一些基础包,不用担心重复,已经存在的会自动忽略或升级
     [[email protected] ~]# yum install gcc make automake bzip2 unzip patch subversion libjpeg-devel
     [[email protected] ~]# wget http://www.tortall.net/projects/yasm/releases/yasm-1.2.0.tar.gz
     [[email protected] ~]# tar zxvf yasm-1.2.0.tar.gz
     [[email protected] ~]# cd yasm-1.2.0
     [[email protected] yasm-1.2.0]# ./configure
     [[email protected] yasm-1.2.0]# make && make install
     [[email protected] yasm-1.2.0]# cd ~
     
     2.2)卸载系统原有的ffmgeg和x.264,没有则跳过
     [[email protected] ~]# yum remove ffmpeg x264

     2.3)先安装一些Mplayer编码库(仅限于64位系统)
     [[email protected] ~]# wget -c http://www2.mplayerhq.hu/MPlayer/releases/codecs/essential-amd64-20071007.tar.bz2
     上面这一步可能在部分VPS上无法下载,比如我的阿里云VPS就提示连接失败,但是可以用本机浏览器下载后上传到VPS的/root根目录去,然后继续下面的操作
     [[email protected] ~]# tar xvjf essential-amd64-20071007.tar.bz2
     [[email protected] ~]# mkdir /usr/local/lib/codecs
     [[email protected] ~]# cp -Rvp essential-amd64-20071007/* /usr/local/lib/codecs/

     编辑下面文件
     [[email protected] ~]# vim /etc/ld.so.conf
     添加以下两行到上面的文件里
     /usr/lib 
     /usr/local/lib
     
     2.4)再安装一些格式转换常用的编码库
     [[email protected] ~]# yum install faac-devel  lame-devel amrnb-devel opencore-amr-devel amrwb-devel  libvorbis-devel libtheora-devel xvidcore-devel
     
     2.5)安装x.264
     [[email protected] ~]# wget ftp://ftp.videolan.org/pub/videolan/x264/snapshots/last_stable_x264.tar.bz2
     [[email protected] ~]# tar xvjf last_stable_x264.tar.bz2
     [[email protected] ~]# cd x264-snapshot-20140219-2245-stable/     (这可能日期有区别,自己ls一下)
     [[email protected] x264-snapshot-20140219-2245-stable]# ./configure –enable-shared –enable-pic
     [[email protected] x264-snapshot-20140219-2245-stable]# make && make install
     [[email protected] x264-snapshot-20140219-2245-stable]#  cd ~

     2.6)安装libvpx
     [[email protected] ~]# wget http://webm.googlecode.com/files/libvpx-v1.2.0.tar.bz2
     [[email protected] ~]# tar xvjf libvpx-v1.2.0.tar.bz2
     [[email protected] ~]# cd libvpx-v1.2.0
     [[email protected] libvpx-v1.2.0]# ./configure –enable-shared –enable-pic
     [[email protected] libvpx-v1.2.0]# make && make install
     [[email protected] libvpx-v1.2.0]# cd ~

     2.7)安装FFmpeg
     [[email protected] ~]# wget http://ffmpeg.org/releases/ffmpeg-2.0.1.tar.bz2
     [[email protected] ~]# tar xvjf ffmpeg-2.0.1.tar.bz2
     [[email protected] ~]# cd ffmpeg-2.0.1
     [[email protected] ffmpeg-2.0.1]# ./configure –enable-gpl –enable-version3 –enable-shared –enable-nonfree –enable-postproc –enable-libfaac –enable-libmp3lame –enable-libopencore-amrnb –enable-libopencore-amrwb –enable-libtheora –enable-libvorbis –enable-libvpx –enable-libx264 –enable-libxvid
     [[email protected] ffmpeg-2.0.1]# make && make install
     [[email protected] ffmpeg-2.0.1]# cd ~
     
     让动态链接库被系统共享
     [[email protected] ~]# ldconfig

     2.8)最后安装mencoder
     [[email protected] ~]# yum install mplayer mencoder flvtool2

     查看一下已经安装好的音频和视频编码器
     查看所有所支持的音频编码
     [[email protected] ~]# mencoder -oac help
     查看所有所支持的视频编码
     [[email protected] ~]# mencoder -ovc help
     具体结果可参考http://www.mplayerhq.hu/DOCS/HTML/zh_CN/menc-feat-selecting-codec.html

3.安装配置Nginx
     
     3.1)先安装各种依赖(nginx需要pcre支持,yamdi用来为flv创建关键帧才能随意拖动)
     [[email protected] ffmpeg-2.0.1]# yum install gcc gcc-c++ openssl-devel zlib-devel pcre pcre-devel yamdi
     
     3.2)下载所需的nginx模块
     第一个是nginx_mod_h264_streaming,让nginx支持flv/mp4流播放
     [[email protected] ~]# wget http://h264.code-shop.com/download/nginx_mod_h264_streaming-2.2.7.tar.gz
     [[email protected] ~]# tar zxvf nginx_mod_h264_streaming-2.2.7.tar.gz
     注意:先要修改一下这家伙的源码,注释掉nginx_mod_h264_streaming-2.2.7/src/ngx_http_streaming_module.c的158到161行
     /* TODO: Win32 */
     //if (r->zero_in_uri)
     // {
     //   return NGX_DECLINED;
     // }

     第二个是nginx-rtmp-module,让nginx支持rtmp/hls协议
     [[email protected] ~]# wget -O nginx-rtmp-module.zip  https://github.com/arut/nginx-rtmp-module/archive/master.zip
     [[email protected] ~]# unzip nginx-rtmp-module.zip
     下载清缓存的模块
     [[email protected] ~]# wget -O ngx_cache_purge.ziphttps://github.com/FRiCKLE/ngx_cache_purge/archive/master.zip
     [[email protected] ~]# unzip ngx_cache_purge.zip
     
     3.3)下载安装nginx
     [[email protected] ~]# wget http://nginx.org/download/nginx-1.2.9.tar.gz
     [[email protected] ~]# tar zxvf nginx-1.2.9.tar.gz
     [[email protected] ~]# cd nginx-1.2.9
     [[email protected] nginx-1.2.9]# ./configure –user=daemon –group=daemon –prefix=/usr/local/nginx/ –add-module=../nginx-rtmp-module-master –add-module=../ngx_cache_purge-master –add-module=../nginx_mod_h264_streaming-2.2.7 –with-http_stub_status_module –with-http_ssl_module –with-http_sub_module –with-http_gzip_static_module –with-http_flv_module
     [[email protected] nginx-1.2.9]# make && make install
     PS:若提示make[1]: *** [objs/addon/src/ngx_http_h264_streaming_module.o] Error 1,请参考3.3.2小节的注意点,然后重新运行./configure这一段 
     
     /**———以下是安装了Tengine后重新编译的代码,模块的路径自己对一下———**//
./configure –user=www –group=www –prefix=/usr/local/tengine/ –add-module=../../../nginx-rtmp-module-master –add-module=../../../ngx_cache_purge-master –add-module=../../../nginx_mod_h264_streaming-2.2.7 –with-http_stub_status_module –with-http_ssl_module –with-http_sub_module –with-http_gzip_static_module –with-http_flv_module –with-http_concat_module=shared –with-http_sysguard_module=shared –with-google_perftools_module –with-http_image_filter_module
     
     运行一下nginx
     [[email protected] ~]# /usr/local/nginx/sbin/nginx
     然后用浏览器你的服务器IP,看到welcome就对了
     或者到nginx的sbin目录下,运行一下nginx -V,看看列表出来的modules对不对

     3.4)各种配置nginx
     编辑/usr/local/nginx/conf/nginx.conf文件,最好用sftp软件(如windows下的flashfxp/Mac下的tramnsmit)下载过来本地编辑。
     贴一下我的配置文件:(目录需要自己改动,我用的是阿里云的数据盘,所以到/mnt/里面去了)
     ———————————————————————nginx配置文件—————————————————
    #filename:nginx.conf
    #user  nobody;
    worker_processes  1;

    error_log  logs/error.log;
    #error_log  logs/error.log  notice;
    #error_log  logs/error.log  info;

    pid        logs/nginx.pid;

    events {
        use epoll;
        worker_connections  1024;
    }

    rtmp {
        server {
            listen 1935;
            chunk_size 4000;

            # video on demand
            application vod {
                play /mnt/media/vod;
            }

            # HLS
            # HLS requires libavformat & should be configured as a separate
            # NGINX module in addition to nginx-rtmp-module:
            # ./configure … –add-module=/path/to/nginx-rtmp-module/hls …
            # For HLS to work please create a directory in tmpfs (/tmp/app here)
            # for the fragments. The directory contents is served via HTTP (see
            # http{} section in config)
            #
            # Incoming stream must be in H264/AAC/MP3. For iPhones use baseline H264
            # profile (see ffmpeg example).
            # This example creates RTMP stream from movie ready for HLS:
            #
            # ffmpeg -loglevel verbose -re -i movie.avi  -vcodec libx264
            #    -vprofile baseline -acodec libmp3lame -ar 44100 -ac 1
            #    -f flv rtmp://localhost:1935/hls/movie
            #
            # If you need to transcode live stream use ‘exec’ feature.
            #
            application hls {
                hls on;
                hls_path /mnt/media/app;
                hls_fragment 10s;
            }
        }
    }

    http {

        include mime.types;
        default_type application/octet-stream;
        sendfile on;
        keepalive_timeout 65;
        gzip on;
        
         #log format

        log_format  access  ‘$remote_addr – $remote_user [$time_local] “$request” ‘
                 ‘$status $body_bytes_sent “$http_referer” ‘
                 ‘”$http_user_agent” $http_x_forwarded_for’;    
        
         #定义一个名为addr的limit_zone,大小10M内存来存储session
        limit_conn_zone $binary_remote_addr zone=addr:10m;     

        server {
            listen 8080;
             server_name localhost;

             # HTTP can be used for accessing RTMP stats
            # This URL provides RTMP statistics in XML
            location /stat {
                rtmp_stat all;
                rtmp_stat_stylesheet stat.xsl;
            }
            location /stat.xsl {
                root /mnt/soft/nginx-rtmp-module-master;
            }
             location /control {
                rtmp_control all;
            }
            location / {
                root /mnt/soft/nginx-rtmp-module-master/test/rtmp-publisher;
            }
        }
        
         server {
            listen 80;
             server_name localhost;
             
            location / {
                    root /mnt/wwwroot;
                    index index.html;
                  }         

             location ~ \.flv$ {
                   root /mnt/media/vod;
                 flv;
                 limit_conn addr 20;
                 limit_rate 200k;
            }
            location ~ \.mp4$ {
                 root /mnt/media/vod;
                 mp4;
                 limit_conn addr 20;
                 limit_rate 200k;
            }

             location /hls {
                # Serve HLS fragments
                alias /mnt/media/app;
            }

             access_log  logs/nginxflv_access.log access;
        }
        
           
    }
     ——————————————————nginx配置文件——————————————
     
4.把自己的电影转换成mp4和flv格式来测试nginx搭的环境

     4.1)准备两部电影,硬盘上随便找,我找了“谍影重重A.mp4”和“鹿鼎记033.rmvb”,尽量找小一点十来分钟的,等下我们还要看完测试一下转换的结果有没有音影不同步的情况。
     我把两部电影重命名为 movie1.mp4和 movie2.rmvb,并上传到服务器/mnt/media/video下面,这里目录用来存放我们的原始视频。还有一个目录是/mnt/media/vod 用来存放转换后的视频。
     我这里的具体目录结构为:
     /mnt/media/video -> 存放原始视频
     /mnt/media/app  -> 存放转成m3u8的视频,供http访问(HLS)
     /mnt/media/vod  -> 存放转换后的flv和mp4视频,供http或rtmp访问

     4.2)用ffmpeg转换mp4文件(ffmpeg不支持rmvb)
     [[email protected] ~]# cd /mnt/media/video/
     [[email protected] video]# ffmpeg -i movie1.mp4 -y -vcodec libx264 -vf scale=”640:-1″ -r 15 -acodec libfaac ../vod/movie1.flv
     -y:文件覆盖,-vf scale=”640:-1”:尺寸调整为宽度640高度自动,-r 15:帧数15fps,这里用libfaac音频编码防止转成ts格式时iPhone没有声音
     
     添加关键帧用来支持拖动播放
     [[email protected] video]# cd ../vod
     [[email protected] vod]# mv movie1.flv movie1-src.flv
     [[email protected] vod]# yamdi -i movie1-src.flv -o movie1.flv
     [[email protected] vod]# rm -rf movie1-src.flv

     接下来测试的话,下载VLC Player到本地测试(VLC有时候会花屏,用JWPlayer就好了),或者不行麻烦的话装一个apache服务器并下载JWPlayer来测试。
     测试播放地址为(地址已失效,请勿访问)
     HTTP形式访问:      http://121.199.47.208/movie1.flv
     RTMP形式访问:      rtmp://121.199.47.208/vod/movie1.flv

     4.3)用mencoder转换rmvb文件
     
     4.4)把flv转换成hls的m3u8
     下载安装segmenter
     [[email protected] ~]# yum install -y curl curl-devel zlib-devel openssl-devel perl perl-devel cpio expat-devel gettext-devel git
     [[email protected] ~]# git clone https://github.com/johnf/m3u8-segmenter.git
     [[email protected] ~]# cd m3u8-segmenter/
     [[email protected] m3u8-segmenter]# gcc -Wall -g m3u8-segmenter.c -o segmenter -lavformat
     [[email protected] m3u8-segmenter]# cp segmenter /usr/bin/
     
     转换成ts,片源大小及清晰度等取决于flv文件,所以转成flv的时候一定要统一起来
     [[email protected] vod]# cd /mnt/media/vod
     [[email protected] vod]# mkdir /mnt/media/app/movie1/
     [[email protected] vod]# ffmpeg -y -i movie1.flv -f mpegts -c:v copy -c:a copy -vbsf h264_mp4toannexb /mnt/media/app/movie1/main.ts
     
     切片
     [[email protected] movie1]# cd /mnt/media/app/movie1/
     [[email protected] movie1]# segmenter -i main.ts -d 10 -p movie1 -m movie1.m3u8 -u http://121.199.47.208/hls/movie1/
     -d 10:每个切片为10秒,-p movie1:切片的名字的前缀
     -u URL地址:m3u8中播放列表的地址前缀,自己cat一下生成的movie1.m3u8就知道什么用 了
     

     测试一下,用iPhone的Safari访问一下http://121.199.47.208/hls/movie1/movie1.m3u8

11月 062015
 

网上看到的文章,有时间搭个试试应该很有意思!

转自:http://redstarofsleep.iteye.com/blog/2123752

Nginx本身是一个非常出色的HTTP服务器,FFMPEG是非常好的音视频解决方案.这两个东西通过一个nginx的模块nginx-rtmp-module,组合在一起即可以搭建一个功能相对比较完善的流媒体服务器.

这个流媒体服务器可以支持RTMP和HLS(Live Http Stream)

从安装开始

Nginx的安装参照这个: http://redstarofsleep.iteye.com/blog/2010391

不同的是在configure的时候需要增加nginx-rtmp-module的支持,下载好nginx-rtmp-module后解压,然后nginx安装时增加这个模块(–add-module),其它都是一样的.

 

Java代码  收藏代码

  1. ./configure –prefix=/usr/local/nginx –with-pcre=/home/user/pcre/pcre-8.32 –with-zlib=/home/user/zlib/zlib-1.2.8 –with-openssl=/home/user/openssl/openssl-1.0.1i  –add-module=/home/user/nginx-rtmp-module  

FFMPEG的安装参照: http://redstarofsleep.iteye.com/blog/2122612

 

nginx配合ffmpeg做流媒体服务器的原理是: nginx通过rtmp模块提供rtmp服务, ffmpeg推送一个rtmp流到nginx, 然后客户端通过访问nginx来收看实时视频流. HLS也是差不多的原理,只是最终客户端是通过HTTP协议来访问的,但是ffmpeg推送流仍然是rtmp的.

 

安装完成后,打开Nginx的配置文件nginx.conf进行配置

首先在里面加入rtmp的配置

Java代码  

  1. rtmp {  
  2.     server {  
  3.         listen 1935;  
  4.   
  5.         application myapp {  
  6.             live on;  
  7.         }  
  8.         application hls {  
  9.             live on;  
  10.             hls on;  
  11.             hls_path /tmp/hls;  
  12.         }  
  13.     }  
  14. }  

然后,针对hls,还需要在http里面增加一个location配置

Java代码  

  1. location /hls {  
  2.             types {  
  3.                 application/vnd.apple.mpegurl m3u8;  
  4.                 video/mp2t ts;  
  5.             }  
  6.             root /tmp;  
  7.             add_header Cache-Control no-cache;  
  8. }  

 

这是一个最简单,最基础的配置, rtmp监听1935端口,如果是hls的话用hls on开启hls,并且为hls设置一个临时文件目录hls_path /tmp/hls; 其它更高级的配置可以参看nginx-rtmp-module的readme,里面有比较详细的介绍其它的配置,并且它还提供了一个通过JWPlayer在网页上播放的例子.

 

保存完配置文件后,启动nginx,通过netstat -ltn命令可以看到增加了一个1935端口的监听.8080是nginx默认的http监听端口.

Java代码  收藏代码

  1. # netstat -ltn  
  2. Active Internet connections (only servers)  
  3. Proto Recv-Q Send-Q Local Address           Foreign Address         State        
  4. tcp        0      0 127.0.1.1:53            0.0.0.0:*               LISTEN       
  5. tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN       
  6. tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN       
  7. tcp        0      0 0.0.0.0:1935            0.0.0.0:*               LISTEN       
  8. tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN       
  9. tcp6       0      0 :::22                   :::*                    LISTEN       
  10. tcp6       0      0 ::1:631                 :::*                    LISTEN    

然后用ffmpeg推流到nginx:

第一个是推到了上面配置的myapp上:

Java代码  收藏代码

  1. ffmpeg -re -i “D:\download\film\aqgy\02.mp4” -vcodec libx264 -vprofile baseline -acodec aac  
  2.  -ar 44100 -strict –2 -ac 1 -f flv -s 1280×720 -q 10 rtmp://server:1935/  
  3. myapp/test1  

第二个推送到hls上:

Java代码  收藏代码

  1. ffmpeg -re -i “D:\download\film\aqgy\02.mp4” -vcodec libx264 -vprofile baseline -acodec aac  
  2.  -ar 44100 -strict –2 -ac 1 -f flv -s 1280×720 -q 10 rtmp://ip:1935/  
  3. hls/test2  

现在我们的流媒体服务器有两个实时流了,一个是rtmp的,另一个是hls的,用流媒体播放器播放一下,流媒体播放器可以用vlc也可以用ffmpeg带的ffplay.手机也是可以播放的.

 

上面这两个流的地址分别是:

第一个就是推送的地址: rtmp://serverIp:1935/myapp/test1

第二个是HTTP地址: http://serverIp:8080/hls/test2.m3u8

 

最后贴上一段对于HLS这个比较特殊的流媒体协议的解释:

       (这段解释来自: http://www.cnblogs.com/haibindev/archive/2013/01/30/2880764.html)

HTTP Live Streaming(HLS)是苹果公司(Apple Inc.)实现的基于HTTP的流媒体传输协议,可实现流媒体的直播和点播,相对于常见的流媒体直播协议,例如RTMP协议、RTSP协议、MMS协议等,HLS直播最大的不同在于,直播客户端获取到的,并不是一个完整的数据流。HLS协议在服务器端将直播数据流存储为连续的、很短时长的媒体文件(MPEG-TS格式),而客户端则不断的下载并播放这些小文件,因为服务器端总是会将最新的直播数据生成新的小文件,这样客户端只要不停的按顺序播放从服务器获取到的文件,就实现了直播。由此可见,基本上可以认为,HLS是以点播的技术方式来实现直播。由于数据通过HTTP协议传输,所以完全不用考虑防火墙或者代理的问题,而且分段文件的时长很短,客户端可以很快的选择和切换码率,以适应不同带宽条件下的播放。不过HLS的这种技术特点,决定了它的延迟一般总是会高于普通的流媒体直播协议。

 

补充:

1. 如果要单纯的以h.264的码流推送摄像头的视频到rtmp server(ip:127.0.0.1),可以这样:
ffmpeg  -f video4linux2  -s 1280x720 -i /dev/video0 -vcodec libx264 -preset ultrafast -vprofile baseline -vlevel 1.0  -s 640x480 -b:v 800k -r 25  -pix_fmt yuv420p -f flv rtmp://localhost/myapp/mystream 
 
ffmpeg -f video4linux2 -vcodec libx264 -vpre default -s 320x240 -i /dev/video0 -f flv rtmp://127.0.0.1:1935/app/stream


2. FFmpeg将网络摄像机的rtsp视频流转换成rtmp流,然后推送rtmp视频流到nginx上面,然后使用手机播放hls的视频流。
使用的命令:
ffmpeg -i rtsp://admin:[email protected] -vcodec copy -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 640×480 rtmp://172.27.35.2:1935/hls/test2

把darkdoor.[001-100].jpg序列帧和001.mp3音频文件利用mpeg4编码方式合成视频文件darkdoor.avi:
$ ffmpeg -i 001.mp3 -i darkdoor.%3d.jpg -s 1024×768 -author skypp -vcodec mpeg4 darkdoor.avi

ffmpeg还支持mov格式:
$ ffmpeg  -i darkdoor.%3d.jpg darkdoor.mov

要查看你的ffmpeg支持哪些格式,可以用如下命令:
$ ffmpeg -formats | less

还可以把视频文件导出成jpg序列帧:
$ ffmpeg -i bc-cinematic-en.avi example.%d.jpg

debian下安装ffmpeg很简单:
#apt-get install ffmpeg

ffmpeg使用语法

ffmpeg使用语法:

ffmpeg [[options][`-i’ input_file]]… {[options] output_file}…

如果没有输入文件,那么视音频捕捉就会起作用。

作为通用的规则,选项一般用于下一个特定的文件。如果你给 –b 64选项,改选会设置下一个视频速率。对于原始输入文件,格式选项可能是需要的。

缺省情况下,ffmpeg试图尽可能的无损转换,采用与输入同样的音频视频参数来输出。

3.选项

a) 通用选项

-L license

-h 帮助

-fromats 显示可用的格式,编解码的,协议的。。。

-f fmt 强迫采用格式fmt

-I filename 输入文件

-y 覆盖输出文件

-t duration 设置纪录时间 hh:mm:ss[.xxx]格式的记录时间也支持

-ss position 搜索到指定的时间 [-]hh:mm:ss[.xxx]的格式也支持

-title string 设置标题

-author string 设置作者

-copyright string 设置版权

-comment string 设置评论

-target type 设置目标文件类型(vcd,svcd,dvd) 所有的格式选项(比特率,编解码以及缓冲区大小)自动设置 ,只需要输入如下的就可以了:
ffmpeg -i myfile.avi -target vcd /tmp/vcd.mpg

-hq 激活高质量设置

-itsoffset offset 设置以秒为基准的时间偏移,该选项影响所有后面的输入文件。该偏移被加到输入文件的时戳,定义一个正偏移意味着相应的流被延迟了 offset秒。 [-]hh:mm:ss[.xxx]的格式也支持

b) 视频选项

-b bitrate 设置比特率,缺省200kb/s

-r fps 设置帧频 缺省25

-s size 设置帧大小 格式为WXH 缺省160X128.下面的简写也可以直接使用:
Sqcif 128X96 qcif 176X144 cif 252X288 4cif 704X576

-aspect aspect 设置横纵比 4:3 16:9 或 1.3333 1.7777

-croptop size 设置顶部切除带大小 像素单位

-cropbottom size –cropleft size –cropright size

-padtop size 设置顶部补齐的大小 像素单位

-padbottom size –padleft size –padright size –padcolor color 设置补齐条颜色(hex,6个16进制的数,红:绿:兰排列,比如 000000代表黑色)

-vn 不做视频记录

-bt tolerance 设置视频码率容忍度kbit/s

-maxrate bitrate设置最大视频码率容忍度

-minrate bitreate 设置最小视频码率容忍度

-bufsize size 设置码率控制缓冲区大小

-vcodec codec 强制使用codec编解码方式。 如果用copy表示原始编解码数据必须被拷贝。

-sameq 使用同样视频质量作为源(VBR)

-pass n 选择处理遍数(1或者2)。两遍编码非常有用。第一遍生成统计信息,第二遍生成精确的请求的码率

-passlogfile file 选择两遍的纪录文件名为file


c)高级视频选项

-g gop_size 设置图像组大小

-intra 仅适用帧内编码

-qscale q 使用固定的视频量化标度(VBR)

-qmin q 最小视频量化标度(VBR)

-qmax q 最大视频量化标度(VBR)

-qdiff q 量化标度间最大偏差 (VBR)

-qblur blur 视频量化标度柔化(VBR)

-qcomp compression 视频量化标度压缩(VBR)

-rc_init_cplx complexity 一遍编码的初始复杂度

-b_qfactor factor 在p和b帧间的qp因子

-i_qfactor factor 在p和i帧间的qp因子

-b_qoffset offset 在p和b帧间的qp偏差

-i_qoffset offset 在p和i帧间的qp偏差

-rc_eq equation 设置码率控制方程 默认tex^qComp

-rc_override override 特定间隔下的速率控制重载

-me method 设置运动估计的方法 可用方法有 zero phods log x1 epzs(缺省) full

-dct_algo algo 设置dct的算法 可用的有 0 FF_DCT_AUTO 缺省的DCT 1 FF_DCT_FASTINT 2 FF_DCT_INT 3 FF_DCT_MMX 4 FF_DCT_MLIB 5 FF_DCT_ALTIVEC

-idct_algo algo 设置idct算法。可用的有 0 FF_IDCT_AUTO 缺省的IDCT 1 FF_IDCT_INT 2 FF_IDCT_SIMPLE 3 FF_IDCT_SIMPLEMMX 4 FF_IDCT_LIBMPEG2MMX 5 FF_IDCT_PS2 6 FF_IDCT_MLIB 7 FF_IDCT_ARM 8 FF_IDCT_ALTIVEC 9 FF_IDCT_SH4 10 FF_IDCT_SIMPLEARM

-er n 设置错误残留为n 1 FF_ER_CAREFULL 缺省 2 FF_ER_COMPLIANT 3 FF_ER_AGGRESSIVE 4 FF_ER_VERY_AGGRESSIVE

-ec bit_mask 设置错误掩蔽为bit_mask,该值为如下值的位掩码 1 FF_EC_GUESS_MVS (default=enabled) 2 FF_EC_DEBLOCK (default=enabled)

-bf frames 使用frames B 帧,支持mpeg1,mpeg2,mpeg4

-mbd mode 宏块决策 0 FF_MB_DECISION_SIMPLE 使用mb_cmp 1 FF_MB_DECISION_BITS 2 FF_MB_DECISION_RD

-4mv 使用4个运动矢量 仅用于mpeg4

-part 使用数据划分 仅用于mpeg4

-bug param 绕过没有被自动监测到编码器的问题

-strict strictness 跟标准的严格性

-aic 使能高级帧内编码 h263+

-umv 使能无限运动矢量 h263+

-deinterlace 不采用交织方法

-interlace 强迫交织法编码 仅对mpeg2和mpeg4有效。当你的输入是交织的并且你想要保持交织以最小图像损失的时候采用该选项。可选的方法是不交织,但是损失更大

-psnr 计算压缩帧的psnr

-vstats 输出视频编码统计到vstats_hhmmss.log

-vhook module 插入视频处理模块 module 包括了模块名和参数,用空格分开

D)音频选项

-ab bitrate 设置音频码率

-ar freq 设置音频采样率

-ac channels 设置通道 缺省为1

-an 不使能音频纪录

-acodec codec 使用codec编解码

E)音频/视频捕获选项

-vd device 设置视频捕获设备。比如/dev/video0

-vc channel 设置视频捕获通道 DV1394专用

-tvstd standard 设置电视标准 NTSC PAL(SECAM)

-dv1394 设置DV1394捕获

-av device 设置音频设备 比如/dev/dsp


F)高级选项

-map file:stream 设置输入流映射

-debug 打印特定调试信息

-benchmark 为基准测试加入时间

-hex 倾倒每一个输入包

-bitexact 仅使用位精确算法 用于编解码测试

-ps size 设置包大小,以bits为单位

-re 以本地帧频读数据,主要用于模拟捕获设备

-loop 循环输入流。只工作于图像流,用于ffserver测试

10月 212015
 

抛砖引玉!!!,学习下相关知识。 (转自:https://imququ.com/post/html5-live-player-3.html)

连续写了两篇有关视频直播的文章之后,有同学问我为什么没有 WebRTC 相关内容。实际上一开始我就说过,我的需求是在移动 WEB 端上直播视频,而移动端浏览器现阶段对「WebRTC 的支持度」非常不乐观,所以我就直接无视它了。但我一时为了标题美观,活生生地把「移动 WEB 端」写成了「HTML5」,所以为了严谨我还是补上这一篇吧。

WebRTC(Web Real-Time Communication),中文一般翻译为「Web 实时通信」。它由一组标准、协议和 JavaScript API 组成,用于实现端到端的音视频及数据共享。与其他浏览器通信机制不同,WebRTC 通过 UDP 传输数据,而我们早已熟知的 XMLHttpRequest、WebSocket 都基于 TCP。

纵观整个浏览器市场,其实只有 Google 和 Mozilla 两家公司对 WebRTC 比较上心,Firefox 22 和 Chrome 23 就开始就支持了它;Microsoft 是搞了自己的一套标准,后续可能会跟 WebRTC 融合,但至少 IE 不会支持现阶段的 WebRTC 标准;Apple 也许是因为有 FaceTime 可以很好地实现 Apple 设备间的多媒体通讯,压根就没打算在 Safari 中增加对 WebRTC 的支持;至于 Opera,换内核后基本等同于 Chrome,这下更要被人无视了。

初识 WebRTC

WebRTC 涉及到很多复杂技术,不过好在浏览器已经把大多数复杂工作抽象成为下面三个 API:

  • MediaStream:获取音频和视频流;
  • RTCPeerConnection:音频和视频数据通信;
  • RTCDataChannel:任意应用数据通信;

MediaStream 对应的是 JS 里的 navigator.getUserMedia() 方法,它负责从底层平台获取音视频流。音视频流经过 WebRTC 音视频引擎的自动优化、编码和解码,就可以直接用或传输到各种目的地用。这里有个 Demo,就是用 getUserMedia 获取视频流,再把每一帧都转成 ASCII 字符播放。总之 MediaStream API 设计得很简单,使用起来也很方便。

RTCPeerConnection 用来建立和维护端到端连接,并提供高效的音视频流传输。整个 WebRTC 提供的 API 中,要数这个最复杂:

首先,要建立端到端连接,不可避免要解决 NAT 穿透问题,RTCPeerConnection 为此引入了 ICE(Interactive Connectivity Establishment)框架。ICE 致力于在端之间建立一条有效的通道,优先直连,其次用 STUN 协商,再不行只能用 TURN 转发。

STUN(Session Traversal Utilities for NAT)协议,解决了三个问题:1)获得外网 IP 和端口;2)在 NAT 中建立路由条目,绑定外网端口,使得到达外网 IP 和端口的入站分组能找到应用程序,不被丢弃;3)定义了一个简单的 keep-alive 机制,保证 NAT 路由条目不会因为超时而被删除。STUN 服务器必须架设在公网上,可以自己搭建,也可以使用第三方提供的公开服务,例如 Google 的「stun:stun.l.google.com:19302」。

TURN(Traversal Using Relays around NAT)协议,依赖外网中继设备在两端之间传递数据。简单说就是通过两端都可以访问的 TURN 服务转发消息,间接把两端连起来。TURN 还会尝试使用 TCP 建立,而不仅仅是 UDP,可靠性大大增强,带宽成本也随着大幅提升。根据 Google 的统计,UDP 服务中,有 8% 左右的情况下需要 TURN。

其次,要建立端到端的信道,还是需要借助服务端来交换和协商一些信息,这个过程被称之为 Signaling。WebRTC 并没有规则 Signaling 必须使用某种协议,而把选择权交给了应用程序。我们可以选用不同方式(XMLHttpRequest、WebSocket),采用已有的 SIP、Jingle、ISUP 等发信协议,来建立信道。

通常,在 WebRTC 应用中,建立信道这一步都是优先走 WebSocket,并支持降级为 HTTP。一来支持 WebRTC 的浏览器肯定都支持 WebSocket;二来 WebSocket 实时性更好一些。特别需要注意的是,WebSocket 只用来辅助建立端到端连接,一旦连接建立,信源在端到端之间的传输就完全不需要服务端了(当然 TURN 这种中继模式就另当别论)。

RTCDataChannel 用来支持端到端的任意应用数据交换。建立 RTCPeerConnection 连接之后,除了可以传输音视频流,还可以打开一个或多个信道用来传输任何文本或二进制内容,这就是 RTCDataChanel。DataChannel API 在使用上跟 WebSocket 非常类似,功能上都可以用来在端到端之间传输数据,但是本质上他们还是有区别的:

首先,WebRTC 端与端之间是对等的,DataChannel 可以由任何一方发起;这与 WebSocket 连接只能由客户端发起不同; 其次,WebSocket 的会话层协议 TLS 是可选的;而 WebRTC 的会话层协议 DTLS 是必须的,这表明通过 WebRTC 传输的数据一定会被加密; 再者,WebSocket 运行在 TCP 之上,每条消息天然有序并可靠;而 DataChannel 可以通过 SCTP 的交付属性选项来指定消息是有序还是乱序,是可靠还是部分可靠,部分可靠时还可以指定使用超时重传还是计数重传策略。

现阶段 DataChannel 运行在下列协议之上:

  • SCTP(Stream Control Transmission Protocol),流控制传输协议,提供了一些与 TCP 类似的特性;
  • DTLS(Datagram Transport Layer Security),传输内容加密,UDP 版的 TLS;
  • UDP(User Datagram Protocol),用户数据报协议,整个 WebRTC 的基础;

这里我并不打算完整地介绍如何从零开始使用 WebRTC,类似的文章网上大把。这里推荐几篇文章,后几篇中文的出自同一个作者,写得比较通俗易懂:

另外还推荐《Web 性能权威指南》这本书,它的第 3 章「UDP 的构成」和第 18 章「WebRTC」对 UDP 内网穿透和 WebRTC 有比较详细的介绍。

一对多直播

前面说过,WebRTC 是用来解决端到端的实时通信问题,也就是说它很适合用在网络电话这种需要双向视频通话的场景上。网上大部分 WebRTC 的 Demo 也都是在页面上放两个 Video,分别来播 localStream 和 RemoteStream。那么究竟 WebRTC 能否用来实现单向一对多直播呢?当然可以,而且貌似还很简单:

  • 首先必须有一个专门负责调用 getUserMedia 采集音视频的页面,我称之为信源服务;
  • 打开直播页面时,建立到信源服务的 PeerConnection,并通过 DataChannel 通知信源服务;
  • 信源服务收到通知后,通过对应 PeerConnection 的 addStream 方法提供直播流;
  • 直播页面监听 PeerConnection 的 onaddstream 事件,将获得的直播流用丢给 Video 播放;

为了方便,我使用了 PeerJS 这个开源项目来验证上面这个过程。PeerJS 对 WebRTC Api 进行了封装,使用更简单。它还提供了用来辅助建立连接的 Signaling 服务,在官网注册一个 Api Key 就能用。也可以通过 PeerJS Server 搭建自己的服务,只需要通过 npm install peer 装好 peer 后,再通过下面这行命令启动就可以了:

peerjs --port 9000 --key peerjs

启动好 Peer Server,在页面中引入 peer.js 就可以开始玩了。首先实现信源服务:

JS//由于其它端都要连它,指定一个固定的 ID var peer = new Peer('Server', {
    host: 'qgy18.imququ.com',
    port: 9003,
    path: '/',
    config: { 'iceServers': [
              { url: 'stun:stun.l.google.com:19302' }
        ]
    }
});

navigator.getUserMedia({ audio: false, video: true }, function(stream) { window.stream = stream;
}, function() { /*...*/ });

peer.on('connection', function(conn) {
    conn.on('data', function(clientId){ var call = peer.call(clientId, window.stream);

        call.on('close', function() { /*...*/ });
    });
});

然后就是直播服务:

JS//随机生成一个 ID var clientId = (+new Date).toString(36) + '_' + (Math.random().toString()).split('.')[1]; var peer = new Peer(clientId, {
    host: 'qgy18.imququ.com',
    port: 9003,
    path: '/',
    config: { 'iceServers': [
              { url: 'stun:stun.l.google.com:19302' }
        ]
    }
}); var conn = peer.connect('Server');

conn.on('open', function() {
    conn.send(clientId);
});

peer.on('call', function(call) {
    call.answer();
    call.on('stream', function(remoteStream) { var video = document.getElementById('video');
        video.src = window.URL.createObjectURL(remoteStream);
    });

    call.on('close', function() { /*...*/ });
});

直播页面通过指定 ID 的方式跟信源服务建立端到端连接,然后通过 DataChannel 告诉信源服务自己的 ID,信源服务收到消息后,主动把直播流发过来,直播页面应答后播放就可以了。整个过程原理就这么简单,这里有一个「完整的 Demo」。

看完上面的 Demo,你也许会想原来使用 WebRTC 直播这么简单,随便找台带摄像头的电脑,开个浏览器就能提供直播服务,那还搞 HLS、RTMP 什么的干嘛。

实际上,现实并没有那么美好,这个 Demo 也就玩玩儿还可以,真正使用起来问题还大着呢!

首先,虽然说在 WebRTC 直播方案中,服务端只扮演桥梁的工作,实际数据传输直接发生在端到端之间,但前面说过仍然会有 8% 的情况完全不能直连。要保证服务的高可用性,还是得考虑部署 TURN 这种复杂而昂贵的中转服务。

其次,Chrome 对每个 Tab 允许连接的终端数有限制,最多 256 个。实际上,在我最新的 Retina Macbook Pro 上,差不多有 10 个连接时,Chrome 就开始变得无比卡,风扇呼呼地转,内存被吃掉 6G,CPU 一直跑满,网络吞吐开始忙不过来,直播服务也开始变得极其不稳定。

所以实际使用方案中,一般还是需要 Media Server 的支持,把「端到多端」变成「端到 Media Server 到多端」的架构。Media Server 可以有更好的性能和带宽,可以自己实现 WebRTC 协议,也就有了支持更多用户的可能。

我找到一个名为 Janus 的 WebRTC Gateway,这个开源项目用 C 语言实现了对 WebRTC 的支持。Janus 自身实现得很简单,提供插件机制来支持不同的业务逻辑,配合官方自带插件就可以用来实现高效的 Media Server 服务。

Janus 官方提供的 Demo 在这里,我也尝试在我的 VPS 上部署了一套。Janus 有个 Streaming 插件,可以接受 GStreamer 推送的音视频流,然后通过 PeerConnection 推送给所有的用户。由于 GStreamer 可以直接读摄像头,也就不用再走 WebRTC 的 MediaStream 获取视频,这样架构就变成了传统的服务器到端了。整个过程比较复杂和曲折,这里不写了,有兴趣的同学可以单独找我讨论。

10月 212015
 

抛砖引玉!!!,学习下相关知识。 (转自:https://imququ.com/post/html5-live-player-2.html)

实际上,HLS 除了上回提到过的延迟很大这个缺点之外,在 iOS 的 Safari 浏览器中还只能全屏播放,也无法做到自动播放,这个是 iOS 系统对 Video 标签统一做的限制。有没有什么办法解决这些问题呢?

我们换个思路,既然原生 Video 有这样那样的问题,不如直接抛弃它。利用 Web Sockets 实现视频流的实时传输,使用纯 JS 进行视频解码,再用 Canvas 逐帧画出图像,这不就实现了直播么。当我有个这个想法之后,初步觉得可行,立马开始一番搜索,收获颇丰。

本文要用到的 Web Sockets 在移动端支持度如下表:

浏览器 支持新标准的版本
iOS Safari 6.1+
Android Browser(Webview) 4.4+
Chrome for Android 42+

(数据来源:caniuse

另外,转换视频格式、生成视频流还需要用到一个神器:FFmepg

Mac 下最简单的做法是通过 Homebrew 安装,直接 brew install ffmpeg 就可以了。Ubuntu 下可以先添加这个源:

sudo add-apt-repository ppa:mc3man/trusty-media

 sudo apt-get install ffmpeg,也能轻松搞定。

Decoder in JavaScript

纯 JS 实现的视频解码器我找到了两个可用的:Broadway  jsmpeg

Broadway 是一个 H.264 解码器,使用 Emscripten 工具从 Android 的 H.264 解码器转化而成,它还针对 WebGL 做了一些优化。

这个解码器支持 mp4 后缀的视频文件,有一些限制:不支持 weighted prediction for P-frames 和 CABAC entropy encoding。例如 iPhone 拍摄的视频它就不支持,可以用 FFmpeg 转一下:

ffmpeg -i in.mp4 -vcodec libx264 -pass 1 -coder 0 -bf 0 -flags -loop -wpredp 0 out.mp4

下面是 H.264 解码示例,视频来自于我的 iPhone 拍摄。用阅读器的同学请点到原文查看。

点击播放

这里还有一个长一点的 Demo,点击查看(加载完 6M 多的 mp4 文件才开始播放,请耐心等待,流量党慎入)。

jsmpeg 则是一个 MPEG1 解码器,它是由作者从头编写出来的,并不像 Broadway 那样是从其他语言翻译而成,所以代码可读性要好很多,代码也更轻量级。

jsmpeg 也对视频文件编码方式有一些要求:不支持 B-Frames,视频宽度必须是 2 的倍数。还是可以用 FFmpeg 来转换:

ffmpeg -i in.mp4 -f mpeg1video -vf "crop=iw-mod(iw,2):ih-mod(ih,2)" -b 0 out.mpg

下面是 MPEG1 解码示例,视频来自于网上。用阅读器的同学请点到原文查看。

点击播放

这里也有一个长一点的 Demo,点击查看(加载完 3M 多的 mpg 文件才开始播放。其实没什么好看的,内容跟上面一样,编码格式不同而已)。

Live Streaming

看到这里,大家肯定会说,这不是要一次性下完全部内容么,怎能称之为直播。是的,要实现直播,还要用 Web Sockets 实现一个实时传输流的服务。FFmpeg 支持很多直播流格式,但不支持 Web Sockets。解决方案是用 FFmpeg 开一个 HTTP 直播流,再开个 Node 服务转一下。

详细一点的过程是这样的,用 NodeJS 监听 FFmpeg 的 HTTP 直播地址,把收到的数据 通过 Web Sockets 广播给所有客户端。核心代码就是下面这几行:

JS//HTTP Server to accept incomming MPEG Stream var streamServer = require('http').createServer( function(request, response) {
    request.on('data', function(data){
        socketServer.broadcast(data, {binary:true});
    });
}).listen(STREAM_PORT);

这段代码来自于上面介绍的 jsmpeg 项目,完整代码在这里。启动这个服务试试(需先装好 ws 模块):

node ~/live/stream-server.js ququ 9091 9092 

三个参数分别是加密串、HTTP 端口、WS 端口。启动后,屏幕上会显示两个地址:

Listening for MPEG Stream on http://127.0.0.1:9091/<secret>/<width>/<height>
Awaiting WebSocket connections on ws://127.0.0.1:9092/

好了,现在就可以使用 FFmpeg 来推送 HTTP 视频流了:

ffmpeg -re -i fox.mpg -codec copy -f mpeg1video http://qgy18.imququ.com:9091/ququ/640/360 

-re 参数表示以视频实际播放速率解码,不加这个参数一般会导致直播速率变得飞快。ququ 是启动 Node 服务时指定的加密串,这样做个简单校验,避免 Node 转发不认识的流。最后的 640  360 是视频的宽高,可以根据实际情况指定。

最后,稍微改一下前面的 Demo,让 jsmpeg 从 WS 流中获取数据就可以实现直播了:

var canvas = document.getElementById('videoCanvas');
var client = new WebSocket('ws://qgy18.imququ.com:9092/');
var player = new jsmpeg(client, {canvas:canvas, autoplay: true});

完整示例地址

Capture Webcam

上面演示的是从文件中获取视频流进行直播,如果把数据源换成摄像头也很容易。FFmpeg 官方 wiki 上有在 Windows / MacOS / Linux 下读取摄像头的详细指南。

以 Mac 为例,它支持 AVFoundation 和 QTKit 两种不同的技术读取摄像头,在比较新的系统(10.7+)上,推荐使用 AVFoundation,QTKit 后续可能会被废弃。

我的 Mac 系统是最新的,直接使用 AVFoundation。首先查看可用摄像头列表:

SHELLffmpeg -f avfoundation -list_devices true -i "" [AVFoundation input device @ 0x7fdd53c228c0] AVFoundation video devices:
[AVFoundation input device @ 0x7fdd53c228c0] [0] FaceTime HD Camera
[AVFoundation input device @ 0x7fdd53c228c0] [1] Capture screen 0 [AVFoundation input device @ 0x7fdd53c228c0] AVFoundation audio devices:
[AVFoundation input device @ 0x7fdd53c228c0] [0] Built-in Microphone

可以看到,编号为 0 的 video 设备正是我想要的摄像头(编号为 1 的设备是电脑屏幕,直播屏幕也挺好玩),下面这行命令就可以捕捉它,并把视频流推到之前的 Node 服务上:

ffmpeg -f avfoundation -i "0" -f mpeg1video -b 500k -r 20 -vf scale=640:360 http://qgy18.imququ.com:9091/ququ/640/360

-b  -r 分别用来指定码率和帧率,可以根据实际情况调整。这样,我摄像头拍摄到的画面,就被 FFMpeg 捕捉下来实时推给远端 Node 服务,实时转化成 WS 数据流,广播给所有终端播放。虽然过程很曲折,但是基本上看不到延迟,体验还是很不错的。

最后,说几个问题:

  • 首先,最大的问题是:这种方案只实现了 Canvas 渲染画面部分,无法支持声音(没声音还好意思叫直播,摔!!!);
  • 其次,JS 解码能力还是稍微有点弱,最后的示例中我有意指定 scale 参数缩小了画面,但在 iPhone 6 Plus 非自带浏览器中还会卡(iOS 第三方 APP 赶紧放弃老系统,果断的把好内核用起来吧~~~);