【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. 脚本检测机制,推流时自动开启,结束时自动关闭
原创文章 29 获赞 21 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43668031/article/details/88622287
今日推荐