【IPFS直播】 利用ipfs協議傳輸進行直播

本系列文章是針對 https://blog.csdn.net/weixin_43668031/article/details/83962959 內容的實現所編寫的。開發經歷包括思考過程、重構和推翻重來。

1.實驗環境以及工具介紹

系統:Ubuntu 18.04.2
IPFS:go-ipfs v0.4.19
Nginx:源碼編譯安裝(添加nginx-rtmp-module)
ffmpeg:通過apt安裝
監視腳本語言:Python

2.基本邏輯架構

去年我思考了如何利用ipfs技術進行直播,構思如下圖:
在這裏插入圖片描述
核心部分是流媒體傳輸,這裏是使用HLS分片成ts小文件進行傳輸的,本節僅完成核心邏輯部分的實現代碼。
在這裏插入圖片描述
這裏要着重聲明一下,Live Server 這個角色,在目前的技術背景下,這必須是個Nginx服務器,但是在未來可以和直播推流端合併在一起。就像近幾年家庭入網時的光貓合併進路由器一樣,“家庭網關”=光纖調制解調器+路由器+交換機+無線交換機,一臺設備集成了不同的傳統設備,就算在視頻直播設備上面,最原始傳統的採集卡只能輸出原始視頻未壓縮的視頻,隨着其他模塊集成,帶有x264壓縮模塊的採集卡就誕生了,可以直接輸出x264格式的視頻流、文件,隨着直播行業的興起,帶有推流功能的採集卡也單身了,把平臺推流相關的配置導入到其中,就可以進行推流了,我相信不久就有包含上述架構的採集卡(硬件)或者打包成一體的軟件突出。

在瀏覽器裏運行ipfs節點時,我無法接受、推送來着非直連節點的信息(貌似沒有轉發?),也許是因爲性能問題,無法處理過大廣播數據,所以只能用瀏覽器上運行的節點直連推流的IPFS節點才能被廣播到,js版本的IPFS未來應該可以做到這一點,因此在這裏我用websocket來直連2個節點。
https://discuss.ipfs.io/t/pubsub-between-go-ipfs-and-js-ipfs/1744
在這裏插入圖片描述
既然這樣,我就只能通過websocket來妥協一下這個問題。

次時代直播網絡架構圖:
在這裏插入圖片描述
在這樣的網絡環境下,想要直播,每個家庭?或者每個內網,都應該至少運行一個IPFS節點,和至少一個RTMP推流拉流的軟件。當然我更希望的是集成在推流軟件裏,推流出來之後就直接是IPFS網絡了。

3.建立傳統直播服務器

安裝軟件的方式有很多,最簡單的不過apt install 安裝,但是在安裝Nginx軟件時發現apt 安裝的版本沒有rtmp模塊,需要源碼編譯安裝,自行加入nginx-rtmp-module模塊。

#!/bin/bash
wget http://nginx.org/download/nginx-1.15.9.tar.gz
wget https://github.com/arut/nginx-rtmp-module/archive/v1.2.1.tar.gz
tar -zxvf nginx-1.15.9.tar.gz 
tar -zxvf v1.2.1.tar.gz 
apt install gcc
cd nginx-1.15.9/
./configure --add-module=../nginx-rtmp-module-1.2.1
make
make install

在編譯時可能會因爲缺少依賴而失敗,補全所有必要的軟件包即可。

默認的安裝目錄在cd /usr/local/nginx/,進入目錄啓動Nginx ./sbin/nginx

修改配置,進入rtmp模塊vim conf/nginx.conf加入以下內容

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

我們重新加載Nginx./sbin/nginx -s reload
這樣傳統的直播服務器就搭建好了,我們來測試一下、
在這裏插入圖片描述
OBS設置:設置->流->自定義->服務器rtmp://172.16.10.170/live,流密鑰demo
OBS裏添加點東西,開始推流
打開一個播放器potplayer,打開->打開連接->rtmp://172.16.10.170/live/demo,然後就可以通過potplayer來觀看直播了

4.利用FFmpeg軟件拉取直播流,切片轉換成ts和m3u8直播切片格式

建立工作文件夾mkdir -p /root/demo /root/demo/ts
進入文件夾cd /root/demo/ts
啓動ffmpeg進程 ffmpeg -nostats -re -i rtmp://172.16.10.170/live/demo -f mpegts -c copy -hls_time 10 -hls_list_size 12 -f hls live.m3u8 > ../ffmpeg.log 2>&1
其中hls_time 是每個切片的時間,hls_list_size 是一個m3u8文件包含多少個切片文件。
然後我們看一下生成的文件長什麼樣子:在這裏插入圖片描述

5.監控日誌,上傳文件到IPFS,推送消息

# -*- coding: utf-8 -*-
#!/usr/bin/env python

import os
import datetime
import pyinotify
import logging
import re
import ipfsapi

base_dir=r'/root/demo'

ipfscache={}
api = ipfsapi.connect('127.0.0.1', 5001)


def printlog():
    try:
        fd = open(os.path.join(base_dir, r'ts/live.m3u8'))
        line = fd.read()
        if line.strip():
            pushtoipfs(line.strip())
        fd.close()
    except Exception, e:
        print str(e)


class MyEventHandler(pyinotify.ProcessEvent):

    def process_IN_MODIFY(self, event):
        try:
            printlog()
        except Exception, e:
            print str(e)

def pushtoipfs(m3u8):
    pattern = re.compile(r'#EXTINF.*\n(.*)') 
    result1 = pattern.findall(m3u8)
    newts = 0
    for item in result1:
        if not ipfscache.has_key(item):
            print('add to ipfs: %s'%item)
            res = api.add(os.path.join(base_dir, 'ts',item))
            ipfscache[item]='/ipfs/%s'%res['Hash']
            newts +=1
        m3u8 = m3u8.replace(item, ipfscache[item])
    # lastTS = ipfscache[result1[-1]]
    if newts > 0:
        res = api.add_str(m3u8)
        lastm3u8 = '/ipfs/%s'%res
    # api.pubsub_pub('live', u'%s\n'%lastTS)

        api.pubsub_pub('livem3u8', u'%s\n'%lastm3u8)

def main():
    printlog()
    wm = pyinotify.WatchManager()
    wm.add_watch(os.path.join(base_dir, r'ffmpeg.log'), pyinotify.ALL_EVENTS, rec=True)
    eh = MyEventHandler()

    # notifier
    notifier = pyinotify.Notifier(wm, eh)
    notifier.loop()


if __name__ == '__main__':
    main()

6.網頁客戶端啓動IPFS節點,加載網頁播放器

<!DOCTYPE html>
<html lang="">
<head>
	<title>LIVE ipfs</title>
	<meta charset="utf-8">
	<link href="video-js.min.css" rel="stylesheet">
	<style type="text/css">body{margin:0;padding:0;}#live{display: none}</style>

</head>
<body>
<video id="live" class="video-js" controls preload="none" width="640" height="360" autoplay style="width: 100%; height: 100vh;">
</video>
<script src="jquery-3.3.1.min.js"></script>
<script src="ipfs.min.js"></script>
<script src="video.min.js"></script>
<script src="videojs-http-streaming.js"></script>
<script>
    let url;
    let ws;
    let ipns;
    let isload = 0;
    let isplayer = 0;
    videojs.Hls.xhr.beforeRequest = (options) => {
        if(options.responseType !== 'arraybuffer'){
            options.uri = url;
        }
        node.pubsub.peers('live_m3u8_'+ipns, (err, peerIds) => {
            if(peerIds.length<1){
                for (let i = ws.length - 1; i >= 0; i--) node.swarm.connect(ws[i]);
            }
        });
        return options;
    };
	const node = new window.Ipfs({
		repo: '/ipfs-' + Math.random(),
		config: {
			Addresses: {
				Swarm: ['/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star/']
			}
		},
        EXPERIMENTAL: {pubsub: true}
	});
    node.on('ready', () => {
        isload = 1;
        for (let i = ws.length - 1; i >= 0; i--) node.swarm.connect(ws[i]);
        node.pubsub.subscribe('live_m3u8_'+ipns,(msg) => {
            url = msg.data.toString();
            if(isplayer===0){
                isplayer = 1;
                const video = document.getElementById('live');
                const source = document.createElement("source");
                source.src = url;
                source.type = 'application/x-mpegURL';
                video.appendChild(source);
                $('#live').css({display:'block'});
                const player = videojs('live');
                setTimeout(()=>{
                    player.play();
                },500);
            }
        });
    });

	$(() =>{
		$.get('config.json',(config)=>{
			ws = config.ws;
			ipns = config.ipns;
		});
		setTimeout(()=>{
			if(isload===0){
                // window.location.reload()
			}
		},15000)
	})
</script>
</body>
</html>

其中config.json 文件內容:

{
	"version":0.1,
	"ipns":"QmerPfuRp964AoNGMWBts9TQBMoPTYQpeq2rSAt3aywS6m",
	"ws":[
		"/ip4/172.16.10.170/tcp/9999/ws/ipfs/QmerPfuRp964AoNGMWBts9TQBMoPTYQpeq2rSAt3aywS6m"
	]
}

其中這裏面的ipns表示我key的地址,將整個文件夾編輯好,推送至ipfs,就可以通過任意公共網關加載視頻了。

7.最終效果預覽

在這裏插入圖片描述
嘗試推送一部電影
在這裏插入圖片描述
目前有比較高的延遲我自己測試有緩衝40秒左右
在這裏插入圖片描述

8.其餘工作

  1. 優化流程,感覺有推流,有拉流,中間有些過程沒有必要。
  2. 對於服務端操作,寫成界面化,方便配置管理。
  3. 外圍程序編寫,直播間標題,直播間描述,彈幕評論系統。
  4. 對歷史分片進行記錄,可以重新發布上傳觀看錄播。
  5. 腳本檢測機制,推流時自動開啓,結束時自動關閉
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章