如何实现可扩展——插件化

许多IDE和编辑器都依靠插件化技术得以拓展其功能,并形成其生态,例如vs、vs code、eclipse、jetbrains系列,当然vue作为一个前端框架也是设计了很不错的插件机制。这些都可以作为借鉴。

要实现流媒体服务器的插件化,就需要把核心功能和拓展功能分离,进行足够的抽象。

三大抽象概念

  • 发布者(Publisher)
  • 订阅者(Subscriber)
  • 房间(Room)

发布者(Publisher)

发布者本质上就是输入流,其抽象行为就是将音频和视频数据压入房间中,换句话说,就是在恰当的时候调用房间的PushVideo和PushAudio函数

源码位置

发布者定义位于monica/publisher.go中

在发布者的定义中有一个InputStream的结构体,用来和房间进行互操作。所有具体的发布者都应该包含这个InputStream,以组合继承的方式成为发布者。该InputStream包含最核心功能就是Publish函数,这个函数的功能就是在房间里面设置发布者是自己,这个行为就是发布。形象的理解就是主播走进了房间。引擎不关心是谁走进了房间,也不关心进来的人会发布什么内容。

发布者插件

所有实现了发布者具体功能的插件,就是发布者插件,这样一来,流媒体的媒体源可以是任意的形式,比如RTMP协议提供的推流,可以由FFMPEG、OBS发布。也可以是读取本地磁盘上的媒体文件,也可以来自源服务器的私有协议传输的内容。

订阅者(Subscriber)

订阅者就是输出流,其抽象行为就是被动接收来自房间的音频和视频数据。

源码位置

订阅者定义位于monica/subscriber.go中

订阅者有两个函数sendVideo和sendAudio用于接收音频和视频数据。这个两个函数会对音视频做一些预处理,主要是实现丢包机制、时间戳和首屏渲染。具体的视频数据会共享读取。然后调用SendHandler将打包好的音视频数据发送到具体的订阅者那里。

订阅者插件

订阅者插件,本质上就是SendHandler函数。具体可以将打包的数据以何种协议输出,还是写入文件,由插件实现。

房间(Room)

房间就是一个连接发布者和订阅者的地方。可以形象的理解为主播的房间,发布者是主播,订阅者就是粉丝观众。房间是引擎的核心,其重要逻辑包括:

  • 房间的创建、查询、关闭
  • 订阅者的加入和移除
  • 发布者的进入和离开。

源码位置

订阅者定义位于monica/room.go中

流媒体服务器的核心是转发二字。当你去研究一款流媒体服务器的时候,会有海量的代码阻碍你看清其核心逻辑。包括:

  • 多媒体格式定义、解析,如Flv、MP4、MP3、H264、AAC等等
  • 传输协议的解析,如RTMP家族、AMF、HTTP、RTSP、HLS、WebSocket等等
  • 各种工具类,用来读取字节的缓冲、大小端转换、加解密算法、等等大部分流媒体服务器都是基于rtmp协议之上扩展而来,这是历史原因造成的,所以功能不能很好的分离,耦合度很高。往往牵一发而动全身。其实所谓的流媒体服务器本质上就是把发布者的数据经过服务器转发到订阅者手里播放,起一个中转作用。至于什么协议格式,什么媒体格式都是属于扩展功能。所以最轻量的服务器应该不包含任何协议格式,任何媒体格式,仅仅只是完成中转。再说的直白一点核心代码就是一个for循环。
  1. for _, v := range r.Subscribers {
  2. v.sendVideo(video)
  3. }

其他都是围绕这个for循环展开。所有的流媒体服务器代码里面都有这个for循环,写法稍有不同,但本质相同。

源码位置

该核心逻辑位于monica/room.go中的Run函数内