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方法。