0 PUBLISH方在publish时,创建BroadcastScope,ClientBroadcastStream作为Provier形式注册到BroadcastScope的pipe上。
IProviderService providerService =(IProviderService) context.getBean(IProviderService.BEAN_NAME); // TODO handle registration failure if(providerService.registerBroadcastStream(conn.getScope(), name, bs)) { bsScope= getBroadcastScope(conn.getScope(), name); bsScope.setAttribute(IBroadcastScope.STREAM_ATTRIBUTE,bs); if (conninstanceof BaseConnection){ ((BaseConnection)conn).registerBasicScope(bsScope); } }
1 PLAY方在play是,PlaylistSubscriberStream将作为一个Consumer注册到BroadcastScope的pipe上。这里的msgIn实际上就是BroadcastScope
</pre><p></p><p></p><pre code_snippet_id="650620" snippet_file_name="blog_20150422_3_8155983" name="code" class="java"> msgIn = providerService.getLiveProviderInput(thisScope, itemName, false); //drop all frames up to the next keyframe videoFrameDropper.reset(IFrameDropper.SEND_KEYFRAMES_CHECK); if (msgIn instanceof IBroadcastScope) { IBroadcastStream stream = (IBroadcastStream) ((IBroadcastScope) msgIn).getAttribute(IBroadcastScope.STREAM_ATTRIBUTE); if (stream != null && stream.getCodecInfo() != null) { IVideoStreamCodec videoCodec = stream.getCodecInfo().getVideoCodec(); if (videoCodec != null) { if (withReset) { sendReset(); sendResetStatus(item); sendStartStatus(item); } sendNotifications = false; } } } //Subscribe to stream (ClientBroadcastStream.onPipeConnectionEvent) msgIn.subscribe(this, null); //execute the processes to get Live playback setup playLive();
2 RED5服务器收到PUBLISH方上来的数据后,在管道上转发。核心代码在BaseRTMPHandler:: messageReceived函数中
case TYPE_AUDIO_DATA: case TYPE_VIDEO_DATA: message.setSourceType(Constants.SOURCE_TYPE_LIVE); if (stream != null) { ((IEventDispatcher) stream).dispatchEvent(message); } break;
3 ClientBroadcastStream::dispatchEvent
l 如果是音视频数据,更新音视频数据的编解码信息。
l 如果是Invoke,那么是控制信息,不需要处理直接返回
l 如果是Notify,看是不是TYPE_STREAM_METADATA,更新metaData
public void onPipeConnectionEvent(PipeConnectionEvent event) { switch (event.getType()) { case PipeConnectionEvent.PROVIDER_CONNECT_PUSH: log.info("Provider connect"); if (event.getProvider() == this && event.getSource() != connMsgOut && (event.getParamMap() == null || !event.getParamMap().containsKey("record"))) { this.livePipe = (IPipe) event.getSource(); //livePipe is pipe of BroadcastScope log.debug("Provider: {}", this.livePipe.getClass().getName()); for (IConsumer consumer : this.livePipe.getConsumers()) { subscriberStats.increment(); } } break;
4 转发数据,向livePipe push数据。livePipe是在bs注册到BrocastScope时,消息通知时赋值的。
if (livePipe != null) { // create new RTMP message, initialize it and push through pipe RTMPMessage msg = new RTMPMessage(); msg.setBody(rtmpEvent); msg.getBody().setTimestamp(eventTime); livePipe.pushMessage(msg); }
再观察一下RED5中InMemoryPushPushPipe,对pushMessage的实现。轮询所有的Consumer,调用Consumer的pushMessage方法。
public void pushMessage(IMessage message) throws IOException { for (IConsumer consumer : consumers) { try { IPushableConsumer pcon = (IPushableConsumer) consumer; pcon.pushMessage(this, message); } catch (Throwable t) { if (t instanceof IOException) { // Pass this along throw (IOException) t; } log.error("Exception when pushing message to consumer", t); } } }
而PlayEngin实现了IPushableConsumer接口,PlayEngin::pushMessage就实现了PLAY方收到数据后的处理,正常情况下它应该就是要转发到客户端连接去了。最终都会调用到msgOut.pushMessage(message);网RTMP连接中写入数据。
到这里RED5服务器收到PUBLISH方上来的数据后,怎么转发给PLAY方的内部机制就介绍清楚了。
但是不管对于PlaylistSubscriberStream还是ClientBroadcastStream,如果往自己对应的连接中发送RTMP消息呢?RED5内部还是使用了管道机制来实现这套逻辑。PlaylistSubscriberStream和ClientBroadcastStream中都有一个成员,
protected IMessageOutput connMsgOut;
connMsgOut实际是一个管道,在stream的start函数中初始化,并注册provider和consumer。
public void start() { log.info("Stream start"); IConsumerService consumerManager = (IConsumerService) getScope().getContext().getBean(IConsumerService.KEY); checkVideoCodec = true; checkAudioCodec = true; firstPacketTime = -1; latestTimeStamp = -1; connMsgOut = consumerManager.getConsumerOutput(this); //创建InMemoryPushPushPipe connMsgOut.subscribe(this, null); setCodecInfo(new StreamCodecInfo()); closed = false; bytesReceived = 0; creationTime = System.currentTimeMillis(); }
consumerManager.getConsumerOutput函数中创建了一个pipe,同时注册了一个ConnectionConsumer,这个ConnectionConsumer就是用来往RTMPConnection中写入RTMP消息的,详细的实现去看ConnectionConsumer的pushMessage的实现即可。
public class ConsumerService implements IConsumerService { /** {@inheritDoc} */ public IMessageOutput getConsumerOutput(IClientStream stream) { IStreamCapableConnection streamConn = stream.getConnection(); if (streamConn == null || !(streamConn instanceof RTMPConnection)) { return null; } RTMPConnection conn = (RTMPConnection) streamConn; // TODO Better manage channels. // now we use OutputStream as a channel wrapper. OutputStream o = conn.createOutputStream(stream.getStreamId()); IPipe pipe = new InMemoryPushPushPipe(); pipe.subscribe(new ConnectionConsumer(conn, o.getVideo().getId(), o.getAudio().getId(), o.getData().getId()), null); return pipe; } }
也就是说一个Stream往它对应的RTMPConnction发送消息的途径是,调用connMsgOut的pushMessage方法。