基於nginx-rtmp-module模塊實現的HTTP-FLV直播模塊nginx-http-flv-module(一)

      本文後續的內容將在這裏更新:《基於nginx-rtmp-module模塊實現的HTTP-FLV直播模塊nginx-http-flv-module(二)》注意:下文的配置很多已經不能用了,因爲現在的實現跟早期的實現相差有點大。而爲了看到整個項目的變遷史,所以保留了下來,下文的更新中提到了爲什麼有些配置項不能再使用的原因。現在使用的配置可查找下文中的README.CN.md。

      近幾年直播行業火爆,開源的直播軟件解決方案有SRS(Simple-RTMP-Server)和nginx-rtmp-module,前者是國人發起的一個優秀的開源項目,目前國內很多公司都使用它作爲直播解決方案,由C++編寫;後者依賴Nginx,以第三方模塊的方式提供直播功能,由C編寫。SRS採用多線程方式(經網友提醒更正:是單線程+協程方式),性能優秀,經受住了衆多場景的考驗,但是SRS3已經閉源(更正:是有一段時間閉源了,現在又開源了);nginx-rtmp-module是採用多進程方式,Nginx的性能優秀,但是據網友測試,nginx-rtmp-module的性能不如SRS,並且nginx-rtmp-module的作者已經很久沒有更新版本了,支持的功能也有限,例如不支持HTTP方式的FLV直播,而這是國內直播行業普遍採用的方式;再如推流不支持upstream,無法分佈式部署功能;還有飽受詬病的播放響應延遲時間很長的問題(即俗稱的不能秒播)等。

      我在nginx-rtmp-module的基礎上實現了基於HTTP方式的FLV直播功能,支持GOP緩存,減少了首屏時間;支持流式和chunked兩種HTTP響應格式;修復nginx-rtmp-module沒有listen配置項時,推拉流失敗的問題;解決nginx-rtmp-module已知的bug,見nginx-http-flv-module,歡迎下載測試和反饋bug,也歡迎提PR。有問題或者建議,可以加Q羣:711969608詳聊。目前已經有很多個人和廠商準備將本模塊商用。據網友反饋,國外已經有直播網站在使用這個模塊。準備商用的廠商中最著名的是華爲,網友和廠商陸續反饋過不少bug,修復後功能已經越來越穩定,在此表示感謝。

2018-06-04:有一家CDN廠商正式上線nginx-http-flv-module,使用RTMP,開啓gop_cache(關閉interleave配置,否則會卡頓或者沒有聲音,目前暫時不知如何修復),他們的客戶包括映客和微吼。

2018-06-28:有一家網絡視頻廠商正式上線nginx-http-flv-module,使用HTTP-FLV方式,不開gop_cache,目前還沒開全量,等待觀察穩定性。

2018-07-28:應一部分網友的需求,已經提供RHEL6(CentOS 6)和RHEL7(CentOS 7)的rpm安裝包,見nginx-http-flv-module-packages

      nginx-http-flv-module與nginx-rtmp-module的功能對比:

功能 nginx-http-flv-module nginx-rtmp-module 備註
HTTP-FLV × nginx-http-flv-module支持HTTPS-FLV
GOP緩存 ×  
vhost ×  
省略listen配置 x  
JSON風格的stat x  
RTMP 302 Beta × nginx-http-flv-module作爲服務器或者客戶端

      如果不想推流,可以用一個現成的直播地址rtmp://live.hkstv.hk.lxdns.com/live/hks。

典型的nginx.conf如下:

worker_processes  4; #Nginx開啓4個子進程,子進程個數最好跟CPU的核心數一樣
worker_cpu_affinity 0001 0010 0100 1000; #CPU的mask,子進程使用它來綁定CPU核心,避免進程切換造成性能損失

error_log logs/error.log error; #錯誤日誌位置和日誌級別,如果使用默認編譯選項,位置爲/usr/local/nginx/logs/error.log,error表示只打印錯誤日誌

events {
    worker_connections  1024; #Nginx處理的最大連接數
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    keepalive_timeout  65;

    server {
        listen       80; #Nginx監聽的HTTP請求端口

        location / {
            root   /var/www; #HTTP請求URL映射到服務器的位置
            index  index.html index.htm; #HTTP請求優先請求的文件,如http://localhost/,如果有index.html在/var/www目錄下,那麼請求的是/var/www/index.html
        }

        error_page   500 502 503 504  /50x.html; #如果遇到這些HTTP請求錯誤,Nginx返回50x.html的內容
        location = /50x.html {
            root   html; #因爲/配置了root /var/www,所以這兒html對應的是/var/www/html,所以50x.html的路徑是/var/www/html/50x.html
        }

        location /live {
            flv_live on; #當HTTP請求以/live結尾,匹配這兒,這個選項表示開啓了flv直播播放功能
            chunked  on; #HTTP協議開啓Transfer-Encoding: chunked;方式回覆
        }
    }
}

rtmp_auto_push on; #因爲Nginx可能開啓多個子進程,這個選項表示推流時,媒體流會發布到多個子進程
rtmp_auto_push_reconnect 1s;
rtmp_socket_dir /tmp; #多個子進程情況下,推流時,最開始只有一個子進程在競爭中接收到數據,然後它再relay給其他子進程,他們之間通過unix domain socket傳輸數據,這個選項表示unix domain socket的路徑

rtmp {
    out_queue   4096;
    out_cork    8;
    max_streams 64; #Nginx能接受的最大的推流數

    server {
        listen 1935; #Nginx監聽的RTMP推流/拉流端口,可以省略,默認監聽1935

        application myapp {
            live on; #當推流時,RTMP路徑中的APP(RTMP中一個概念)匹配myapp時,開啓直播
            gop_cache on; #開啓GOP(Group of Picture)緩存,播放器解碼時,收到一個完整的GOP纔會開始播放,這個是減少播放延遲的選項
            pull rtmp://live.hkstv.hk.lxdns.com/live/hks; #如果懶得推流,那可以用這個,香港衛視的直播推流

        }

        #以下配置項已廢棄

        application app1 {

            proxy_pass rtmp://host(ip or domain name)[:host]/app2; #將推流反向代理到上游服務器,並將app1自動轉化爲app2

            #proxy_pass rtmp://backend; #將推流反向代理到上游服務器,見upstream配置

        }
    }

    server {

        listen 1935;

        server_name *.test.com; #或者www.test.*/www.test.com

        application myapp {

            live on;

            gop_cache on;

        }

    }

    #以下配置項已廢棄,原因在下文更新中

    upstream backend {

        #開啓負載均衡

        server host1:port1;

        server host2:port2;

    }
}

啓動Nginx,在vlc播放器中以“網絡”方式打開媒體,填入http://localhost/live?stream=hks(已廢棄)即可。

通用URL(已廢棄):http://example.com[:port]/dir?[srv=index&app=xxx&]stream=xxx。

如果http配置塊裏的監聽端口不是80(默認),那麼必須加上:port,如:8080。

如果rtmp配置塊裏有多個server配置塊,如果想要播放的流的配置是在第二個server配置塊中,那麼必須加上srv=1(從0開始計數)。

如果rtmp配置塊中的某個server塊下有多個application配置塊,如果想要播放的流的APP(RTMP中的一個概念)的名稱是test,那麼必須指明app=test,stream對應的是推流的名稱。

推流的通用命令:ffmpeg -i -re xxx.mp4(或者與RTMP兼容的媒體文件)-vcodec copy -acodec copy -f flv rtmp://example.com[:port]/app/stream,後面也可以像HTTP的URL那樣加參數,目前沒仔細研究過,如果想推流到myapp,那麼app換成myapp,stream隨便取名,播放的時候跟它保持一致就可以。

其他的見nginx-rtmp-module的wiki說明。

測試效果圖如下:

 

2017-09-18更新:

反向代理和負載均衡的功能已經基本可用,但是之前併爲考慮到如果推流數很多,例如1000路推流,這可能對服務器造成沉重的負擔。那爲什麼HTTP協議使用反向代理和負載均衡沒有這個問題呢?那是因爲HTTP請求佔用的帶寬很有限,負載瞬時可能很高,但是不會太持久。

2017-10-07更新:

虛擬主機功能已基本可用,即可以像HTTP配置那樣配置server_name了,由於可以通過虛擬主機查找配置,所以不再支持參數srv=index,添加了一個參數port,如果不指定,默認爲1935,用於指定以該port查找推流對應的配置。通用URL變爲:http://example.com[:port]/dir?[port=xxx&]app=xxx&stream=xxx

2017-11-10更新:

RTMP的302重定向已基本可用,但是由於很多播放器不支持重定向,所以該功能很受限,目前只有JW Player測試通過,VLC無法解析返回的重定向信息,其他播放器沒有測試過。關於RTMP的302重定向,可以參考Adobe的官網裏的application.redirectConnection部分說明:https://helpx.adobe.com/adobe-media-server/ssaslr/application-class.html。

設置如下:在server塊或者application塊中添加配置,假設推流時的app爲myapp,要重定向到test,保持流的名稱不變:

rewrite '^/app/(.*)' '/test/$1';

這樣,就可以在本機上將推流或者播放都從app重定向到test上。

如果要推流到其他主機,則可以設置爲:

rewrite '^/app/(.*)' rtmp://otherhost:otherport/otherapp/$1;

這樣,就可以將本機上的推流或者播放都重定向到其他主機上,這也是一種負載均衡的方法。

PS:不太願意將rewrite分支merge到master上,畢竟受限太多,功能有點雞肋。

2017-11-12更新:

今天在筆記本上進行壓力測試,用的是srs給的測試工具,而它不支持推mp4文件流,只支持flv格式,結果一測試就出現問題,HTTP方式播放無法正常運行,查了下代碼,已經修復bug。

2017-11-22更新:

有網友提到同時使用HTTP和RTMP方式直播時,停止RTMP方式播放會導致HTTP方式播放也停止,這個bug幾天前測試的時候已經發現,不過最近由於工作比較忙,沒來得及改,今天修復了這個bug。

2017-12-10更新:

評論中有網友指出不知道如何使用HTTP方式播放直播流,可以查看github上的README.CN.md,這個文件是中文說明,README.md是英文說明。這兩天專門更新了一下這兩個文件,沒有添加新的功能。測試截圖如下,其中網頁是用RTMP方式播放,VLC是HTTP方式播放:

插個使用flv.js播放的截圖(2018-04-06):

2017-12-30更新:

2017年最後一次更新,由於之前已經提及爲什麼反向代理和負載均衡在實際生活中不太實用,所以已經把README文件裏的反向代理和負載均衡的說明刪除了,不過代碼還沒有刪除,後續會陸陸續續刪除。對於評論中有網友提到的問題,有些還沒修復,我很抱歉,平時上班比較忙,年底連續上了12天班,通宵1晚,所以來不及修復問題。有興趣的網友可以自己hack代碼,代碼風格是嚴格按照nginx的官方要求格式寫的,我自認爲看着還行,至於有些邏輯問題,我也沒搞太清楚,只知道那樣寫沒問題。

最後,最近重寫了http-flv直播的功能,組裝數據和發送全部使用HTTP的框架,不再使用一些“裸露”的組裝數據的方法,如"HTTP/1.1 200 OK"CRLF,發送也使用ngx_http_send_header和ngx_http_output_filter完成,不再使用自定義的發送函數,爲什麼有這個想法,源於nginx從1.3.9版本後原生支持HTTP的chunked傳輸,沒有必要再自己搞一套組裝和發送chunked數據的方法,並且對於非chunked傳輸,nginx的HTTP模塊更不在話下,所以乾脆全部用nginx的HTTP框架了。

最後,上面說的代碼不會提交了,因爲我發現有人fork代碼後,又刪除了fork,然後在自己的代碼里加了些我的項目裏的代碼,儘管改了變量名什麼的,還是看得出痕跡。BSD-2-Clause開源協議本來要求很簡單,你修改,再發布甚至商用,LICENSE文件裏署上原作者信息即可。這點都辦不到,那我也只能小心眼了。

2018-01-02更新:

反向代理和負載均衡的代碼已經從master分支刪除,vhost分支與master分支代碼是一樣的,upstream分支還保留有反向代理和負載均衡的代碼,有需要的可以查看這個分支,後續不再維護這兩個功能。

2018-01-03更新:

感謝一些網友指出nginx-http-flv-module因爲nginx的版本變更造成不能編譯的問題,目前已經把一些已發現的兼容問題修復了,測試到最舊的nginx版本是1.2.6,考慮到nginx-1.2.6已經是2012年的版本了,所以絕大多數情況下應該不會使用比它更舊的版本,所以不再測試nginx-http-flv-module和更舊的nginx版本的兼容性了。

2018-01-12更新:

最近使用srs-bench推流測試nginx-http-flv-module的穩定性,發現在播放測試視頻第三遍時(偶爾第一遍、第二遍)會出現CPU使用率暴增,nginx不接受任何服務,播放器畫面靜止不動的問題(我用過的播放器都會出現這問題,所以不是播放器的問題)。經調試,發現是在釋放已使用的鏈表(並不是釋放內存,是把內存鏈表鏈入一個free指針)時,無限循環了,即已使用的鏈表形成了環。後來確認是重複釋放已使用的鏈表造成的問題,修改代碼後,播放測試視頻十幾遍(半個多小時)沒再出現問題,代碼已經更新。謹慎猜測nginx-rtmp-module也有此問題,但是沒有測試過。

2018-02-07更新:

有網友提交代碼了,包括定時輸出日誌和FCPublish等命令的處理,代碼已經合併。另外有網友在服務器上試用,32GB的內存6個小時耗盡,顯然有內存泄漏,目前已經修復。不得不佩服nginx-rtmp-module的原作者,內存鏈表使用了引用計數器,分配和釋放對計數器的操作避免了多次釋放造成鏈表環的形成。還修復了一個因爲GOP緩存數目爲2時,會造成瞬間發送數據的速率太高,造成播放器來不及接收數據,進而造成播放卡頓的bug。用wireshark抓包可以看出有'TCP Window Full'的問題,經查造成此問題的原因就是播放器來不及接收數據。

2018-02-27更新:

有網友提出想在Windows上運行帶有nginx-http-flv-module的nginx,而我之前一直將重心放在Linux上,並且Mac OS X上也能編譯通過,但是沒怎麼測試,昨晚在Windows上編譯時,發現好多編譯錯誤,並且如果開啓了“chunked on;”配置項,播放會崩潰,現一併修復了這些bug,非常感謝網友們的測試與建議。

其他文章:

基於nginx-rtmp-module模塊實現的HTTP-FLV直播模塊nginx-http-flv-module(二)

基於nginx-rtmp-module模塊實現的HTTP-FLV直播模塊nginx-http-flv-module(三)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章