ZLMediaKit 推拉流概述
最近在看 ZLMediaKit 的源码,它是一个基于 C++11 的高性能运营级流媒体服务框架。由于对其推拉流的整体流程比较感兴趣,查阅相关资料之后,再结合源码,整理出了一个概要的流程。
概述
在 ZLMediaKit 中,收到某种协议的推流之后,会将转换为多种协议的音视频流,以便不同的客户端进行播放,其整体流程如下,并以 RTMP 推流,RTSP 拉流举例:
概念:
- MediaSource: 媒体源,任何 rtsp/rtmp 的直播流都源自该对象,每种协议都有自己的媒体源对象,如 RtmpMediaSource 和 RtspMediaSource;
- RtmpSession & RtspSession:会话对象,表示一个网络连接,每种协议都有其会话对象,通过它,可以实现协议的解析及数据的接收和发送;
- MediaSourceMuxer:媒体源复用器,使用它可以实现流媒体的协议转换,它接收两类数据:1)Track,轨道数据,它用来描述音视频元数据,一个轨道代表一股音视频流;2)Frame,音视频媒体数据;
- MultiMediaSourceMuxer:媒体源复用聚合类,它包含所有协议的媒体源复用器;
- RtspMediaSourceMuxer:Rtsp 媒体源复用器,它负责将音视频媒体数据封装为 Rtsp 协议的流媒体。每一个媒体源复用器都包含一个同协议的 MediaSource 源;
- RingBuffer:环形缓存器,它用来缓存媒体数据;
- _RingReaderDispatcher:媒体数据分发器,由于拉流对象分布在不同的线程上,由该对象完成在本线程上将数据分发给拉流对象;
- RingReader:拉流对象,每一次拉流都会生成一个该对象。
拉流回调
每一次拉流的操作,都会生成一个 RingReader 对象,在该对象上注册回调函数,当有媒体数据时,调用该回调函数,最终由 Session 对象写入到网络连接中,从而实现拉流。以 Rtsp 拉流为例,注册回调由以下方法实现:
1 | // RtspSession.cpp |
可见,RingReader 关联绑定到 MediaSource 的 RingBuffer 上,当 RingBuffer 有数据时,则触发该回调。
媒体分发
ZLMediaKit 中使用了多线程模型,拉流 RingReader 对象被平均负载到多个 poller 线程上(poller 线程代表工作线程,一个 poller 线程负责读取多个网络连接的数据)。当推流时,媒体数据会写入到 RingBuffer 中,由于推流线程和拉流线程可能不是同一个线程,为了提高分发的效率。推流线程向每个 poller 线程添加一个异步任务,具体的拉流操作由异步任务执行。具体代码如下:
1 | // RingBuffer 对象 |
_dispatcher_map 定义如下:std::unordered_map<EventPoller::Ptr, typename RingReaderDispatcher::Ptr, HashOfPtr> _dispatcher_map;
,它是一个 map 对象,第一个值是 EventPoller 对象(Poller 线程对象)指针,第二个值是 _RingReaderDispatcher (RingReaderDispatcher 是其别名)对象指针,在该对象中关联了 RingReader 对象。这个 map 对象维护了与 RingBuffer 对象相关联的 Poller 线程及 RingReaderDispatcher 对象的映射关系。通过遍历 _dispatcher_map 对象,便可获取所有的拉流对象。
向 Poller 线程添加一个异步任务,该任务只有一个功能,便是向 RingReaderDispatcher 写入数据,其代码如下:
1 | // _RingReaderDispatcher 对象 |
_reader_map 中维护了当前 Poller 线程中所关联的 RingReader 的对象,遍历所有 RingReader 对象,并最终触发 onRead 回调,完成媒体数据的推送。
说明:
RingBuffer 中会缓存 GOP 数据,为了叙述的简洁,省略了该部分内容,不过不影响整体流程的准确性。