zlmediakit的优势就是支持多种媒体容器和媒体协议。我从推流和拉流的两个角度,梳理出了转流的核心骨架。
推流
协议和容器格式的转换,最基本的内核就是音视频数据的扭转。
对视频而言就是,解封装帧数据,组帧,封装帧。
对音频而言简单些,只有解封装,封装。
如下是rtsp中的视频转换为rtmp,mp4,webrtc的简单示意图。
源端是rtsp的推流,目的端是各种协议的拉流。
- 最开始的是rtsp信令协商。
- 信令协议协商成功后,通过rtp传输媒体数据。
- 从rtp包中解出视频nalu数据。
- 组成完整的nalu数据,再根据具体的目的协议或容器进行封装。
- 目标协议也是媒体的协商,在媒体协商完成后,由拉流端主动发起转流。
下面是以rtsp推流中的整个流程为例子,画了一个视频的流转图(音频也类似)。
当一个rtsp推流端推流后,媒体流会经过解封装,组帧再经过封装成不同协议放到对应的ringbuffer
中。流程图中可以很明显的看到整个过程。
对推到ZLMediaKit的流,都会固定的产生FMP4MediaSource(有宏控制)
,RtmpMediaSource
,RtspMediaSource
,TSMediaSource
,MP4Recorder(mp4存储,按需产生)
,HlsRecorder(Hls存储,按需产生)
,RingBuffer(未经过封装的裸帧数据)
。
这些对象都会注册到全局的MediaSource
容器中,就是s_media_source_map
,下面是它的定义
using StreamMap = unordered_map<string/*strema_id*/, weak_ptr<MediaSource> >;
using AppStreamMap = unordered_map<string/*app*/, StreamMap>;
using VhostAppStreamMap = unordered_map<string/*vhost*/, AppStreamMap>;
using SchemaVhostAppStreamMap = unordered_map<string/*schema*/, VhostAppStreamMap>;
static SchemaVhostAppStreamMap s_media_source_map;
就是多个unorder_map
的套娃,记录了流的信息和对应的MediaSource
对象。当有需要该流时,会根据流信息在容器中找对应的MediaSource
。
每路推流(不同的stream id)都会这样的流程,产生几个对应协议的MediaSource
对象。
所以在媒体层面,不管该流是否有被消费(拉流),媒体层面的rtsp,rtmp,fmp4,ts数据都已准备好。那么在消息(拉流)时,只需要媒体信令完成,就可以直接发流了。
拉流
上面了解了推流的处理流程,那么拉流的流程就比较好理解了,如下图:
- 以rtmp协议拉流,rtmp的信令协商处理最终会放到
RtmpSession
中处理。 - 在协商完成后,会在
s_media_source_map
找到MediaSource
。 - 再通过
MediaSoruce
取到RingBuffer
对象。 - 通过调用
RingBuffer
对象的attch
方法,打通转流。
下面是rtmp拉流与源端对接的代码,位于RtmpSession::sendPlayResponse
中。
_ring_reader = src->getRing()->attach(getPoller());weak_ptr<RtmpSession> weak_self = static_pointer_cast<RtmpSession>(shared_from_this());_ring_reader->setGetInfoCB([weak_self]() {Any ret;ret.set(static_pointer_cast<SockInfo>(weak_self.lock()));return ret;});_ring_reader->setReadCB([weak_self](const RtmpMediaSource::RingDataType &pkt) {auto strong_self = weak_self.lock();if (!strong_self) {return;}size_t i = 0;auto size = pkt->size();strong_self->setSendFlushFlag(false);pkt->for_each([&](const RtmpPacket::Ptr &rtmp){if(++i == size){strong_self->setSendFlushFlag(true);}strong_self->onSendMedia(rtmp);});});
通过RingBuffer
的attach
方法将RtmpSession
对象关联到源buffer中,再将数据发送出去。
这就是ZLMediaKit转流的骨架,当然整个流程涉及到很多"皮毛",比如媒体格式的匹配,时间戳的转换,同步等。掌握了骨架在解读细节就不会困难了。