1. 程式人生 > >基於nginx-rtmp-module模組實現的HTTP-FLV直播模組nginx-http-flv-module(一)

基於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

2018-10-28:現在nginx-htt-flv-module還有一個問題,多程序模式下,接收到publish的程序auto_push到另外的程序時,如果配置裡存在多個server塊,當請求的是非第一個server塊時,可能造成播放失敗,這是因為auto_push依靠unix domain socket通訊,但是unix domain socket沒有埠資訊,所以,當播放和釋出在不同的程序上時,由於auto_push無法判斷查詢的是哪個server塊(預設是第一個),播放會失敗。單程序沒有這個問題,因為單程序沒有auto_push,多程序模式下,只有一個server塊配置時也沒有問題。

      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,非常感謝網友們的測試與建議。

其他文章: