之前偶尔会在B站开直播播放一些动画,用OBS或者哔哩哔哩直播姬推流,方便是方便,功能也算齐全,但是电脑需要一直开着(感觉还挺耗电),而且用轻薄本推流总是CPU占用率高(估计是参数设置有问题,渣配置顶不住)。刚好手上有台树莓派,于是突发奇想:树莓派耗电远比电脑小,能不能用树莓派来做些简单的推流呢?后面便折腾了一下,下面是大概的流程(基本上都是参考网上的文章,资料还是挺丰富的,整个过程只需要敲敲命令行,写写简单脚本,无编程,适合小白的我)。
一、树莓派系统安装
这个网上有很多教程了,这里就简单带过(需要树莓派、tf卡,读卡器,电源)
1、去官网下载一个系统镜像,镜像有三个版本,推荐选 Raspberry Pi OS with desktop ,这个带图形界面。
2、烧录系统,我这里使用USB Image Tool,感觉还挺好用的
3、更改apt软件源。raspbian与Ubuntu都是基于Debian的Linux系统,所以在Ubuntu上常见的apt、dpkg这些东西到了Raspbian都是一样的。更换后使用apt-get安装软件的体验会好很多
4、安装远程桌面。一般来说通过SSH管理树莓派就可以,但是远程登录桌面,就得在树莓派上安装VNC Server,安装后就可以很方便的操作树莓派了。
二、测试环境搭建
1、安装nginx和rtmp(具体参考https://shumeipai.nxez.com/2017/11/01/build-rtmp-stream-live-server-with-raspberry-pi.html)
搭建完之后就可以做具体的测试了,同时配合vlc播放本地rtmp流。
2、使用ffmpeg推流(参考https://shumeipai.nxez.com/2017/06/05/use-raspberry-pi-to-send-music-to-the-bilibili-for-24-hours.html)
树莓派系统已经安装好ffmpeg,可以直接使用。这里展示比较简单的推流命令:
3、后续的推流优化
目前想要达到的效果:循环推一部番剧。
单单靠上面这句命令只能播放一个固定的视频一次,满足不了我的需求,后面需要进行修改
三、循环推流
1、按文件列表推流
上面写到的命令只能推一个视频一次,而一部番剧有复数的视频,那么如何推整部番剧呢?这里用到ffmpeg里的一个参数:
这里参考https://blog.csdn.net/sinat_14826983/article/details/82597272
得到:
playlist.txt的格式如下:
2、循环推流
上面的语句执行后,播放完列表里的文件后就停止推流了,那么想要24小时推流,就需要让ffmpeg不停地推流。我翻了一下网上的资料,首先是尝试使用一个参数:
(参考https://blog.csdn.net/cai6811376/article/details/74783269/)
后来发现使用这个参数播放文件列表,播放完一遍列表后就会报错,只能用于文件的循环播放。这个不行的话那就给它弄个脚本做while循环吧。
先新建一个 .sh文件(例如test.sh),加入以下内容:
保存以后执行 sh test.sh 即可。
四、尝试无缝推流
通过上面的命令进行推流,发现单个ffmpeg命令执行完后,rtmp播放会出现短时间的等待(黑屏转圈圈),那么有什么办法可以无缝推流,使播放页面不会出现等待呢?
首先是伪无缝推流,即使用复制粘贴大法,文件列表通过复制粘贴做得很长,这样子的话很长时间才会出现一次缓冲等待,比较简单粗暴(我喜欢),但是不算真的无缝推流。
现在的想法是:不想因为ffmpeg推流结束后再重新开始推流的这个过程导致rtmp播放出现缓存等待,那就让ffmpeg持续运行输出数据。上面的ffmpeg命令行是通过获取输入流,再通过ffmpeg内部程序对输入流进行解封装、解码、重新编码、重新封装等操作(不知道有没有理解错误),然后进行输出,处理完毕后,程序就会结束,命令行运行完毕。那么只要让输入流一直有数据,那么ffmpeg就不会停止运行,也会一直有输出流输出到rtmp服务器。上网找了一下资料,翻到了一个B站up主的视频
虽然最后结果还是不太符合我的需求,但是里面提到的管道给了我启发。管道可以作为一个媒介,管道一端用于接收需要推送的媒体流,另一端则输出给ffmpeg推流到rtmp服务器,那只要管道输出端一直有数据给到ffmpeg推流,那ffmpeg就能一直运行。
管道又分为匿名管道和命名管道,我一开始尝试使用匿名管道发现无法解决问题,匿名管道具有一定的局限性:首先,这个管道只能是具有血缘关系的进程之间通信;第二,它只能实现一个进程写另一个进程读,而如果需要两者同时进行时,就得重新打开一个管道。于是我尝试使用命名管道,关于命名管道:
1、与管道的区别:提供了一个路径名与之关联,以FIFO文件的形式存储于文件系统中,能够实现任何两个进程之间通信。而匿名管道对于文件系统是不可见的,它仅限于在父子进程之间的通信。
2、FIFO是一个设备文件,在文件系统中以文件名的形式存在,因此即使进程与创建FIFO的进程不存在血缘关系也依然可以通信,前提是可以访问该路径。
3、FIFO(first input first output)总是遵循先进先出的原则,即第一个进来的数据会第一个被读走。
根据命名管道的特性,可以通过一个命令行对管道写入数据,再通过另一个命令行从管道读取数据,于是我做了以下操作:
1、新建命名管道:
2、往命名管道输入数据
3、从管道读取数据并推流
运行后,推流的命令行报错:
这是因为输入端媒体流的封装格式问题吗?这时想到了可以试试改用TS格式封装(TS的全称则是Transport Stream,即传输流,DVD节目中的MPEG2格式,是MPEG2-PS,MPEG2-TS格式的特点就是要求从视频流的任一片段开始都是可以独立解码的。现主流视频网站都采用这种模式。)
然后再重新运行,成了!(目前这个方法是基于Linux系统环境下实现的,windows系统下的命名管道好像无法用命令行创建和调用;另外这个方法的稳定性还不确定,短时间测试了一下还是能维持运行的)
五、改进优化
目前上面的方法应该是可以实现基本的无缝推流(遇上网络波动我就不懂怎么处理了),后面说的则是一些方便推流的小操作。
例如说,当网络波动导致推流卡死或者手动关闭推流进程,导致管道读端关闭,使整个管道关闭,输入进程会由于写入失败而关闭,再次运行则重新按文件列表顺序推流(本来番剧播放中间,重新开始后又要从第一集开始播放);又例如,手动关闭输入进程,推流进程会读取完管道里的数据后关闭。如果我想要输入进程或者推流进程其中一个关闭后,另一个不关闭,而是进入等待状态的话,那就需要维持管道的开启。
这里说的是有关命名管道的一些情况:
1、所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程从管道的读端读数据时,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
2、指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据时,管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
3、所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
4、指向管道读端的文件描述符没关闭(管道的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
当管道的写和读都已经打开后,管道读端的引用计数等于0或者写端的引用计数等于0,管道就会关闭,需要重新操作。简单来说,当上面整个操作流程开始后,中断其中一个进程,最后都会导致管道关闭,那么想要维持管道的开启,需要管道读端的引用计数和写端的引用计数都大于0。
于是我想了个办法,利用管道的阻塞特点,通过新建两个新的管道文件keep1、keep2,一个指向push的写端,另一个指向push的读端,使push读端和写端的引用计数都不为0,具体如下:
经过测试:
1、推流进程关闭时,输入进程进入阻塞状态,重新开启推流进程,输入进程恢复运行,输入没有重置
2、输入进程关闭时,推流进程进入阻塞状态,重新开启输入进程,推流进程恢复运行,无需重新打开
六、总结
基于Linux系统下通过ffmpeg推流到本站,只需要几个简单的步骤:
1、新建3个命名管道,名字随意
2、创建输入脚本push.sh,填入
3、创建推流脚本send.sh,填入
4、打开四个终端窗口,分别输入:
上面几个命令的执行顺序应该不影响最终结果,不想开这么多终端窗口,那就后台运行吧,不过push和send在后台运行的话应该就看不到ffmpeg的打印信息了。
文章写到这里就结束了,其实想要做得更好,应该还是需要码代码的,可惜我不懂,有机会再研究研究。对上面使用命令行推流有兴趣的小伙伴可以尝试一下,若发现文章有什么错误的地方,十分欢迎大家指出,有什么想法也欢迎交流,谢谢!