red5源码分析---13
来源:互联网 发布:java仓库管理系统代码 编辑:程序博客网 时间:2024/05/18 00:03
red5源码分析—play命令分析
本章开始分析play命令,先来看客户端的代码,定义在BaseRTMPClientHandler中,
public void play(Number streamId, String name, int start, int length) { if (conn != null) { int channel = getChannelForStreamId(streamId); ping(Ping.CLIENT_BUFFER, streamId, 2000); PendingCall receiveAudioCall = new PendingCall("receiveAudio"); conn.invoke(receiveAudioCall, channel); PendingCall receiveVideoCall = new PendingCall("receiveVideo"); conn.invoke(receiveVideoCall, channel); Object[] params = new Object[3]; params[0] = name; params[1] = (start >= 1000 || start <= -1000) ? start : start * 1000; params[2] = (length >= 1000 || length <= -1000) ? length : length * 1000; PendingCall pendingCall = new PendingCall("play", params); conn.invoke(pendingCall, channel); } else { } }
BaseRTMPClientHandler的play一共向服务器发送了三条命令,receiveAudio、receiveVideo以及play,其中play命令包含了三个参数,分别是流的名称name,起始位置start,以及流的长度length。下面就开始分析服务器如何处理这三条命令。
play命令
根据前面几章的分析,play命令到达服务器后,最后会调用StreamService的play函数,代码如下,
public void play(String name, int start, int length, boolean flushPlaylist) { IConnection conn = Red5.getConnectionLocal(); if (conn instanceof IStreamCapableConnection) { IScope scope = conn.getScope(); IStreamCapableConnection streamConn = (IStreamCapableConnection) conn; Number streamId = conn.getStreamId(); if (StringUtils.isEmpty(name)) { return; } IStreamSecurityService security = (IStreamSecurityService) ScopeUtils.getScopeService(scope, IStreamSecurityService.class); if (security != null) { Set<IStreamPlaybackSecurity> handlers = security.getStreamPlaybackSecurity(); for (IStreamPlaybackSecurity handler : handlers) { if (!handler.isPlaybackAllowed(scope, name, start, length, flushPlaylist)) { return; } } } boolean created = false; IClientStream stream = streamConn.getStreamById(streamId); if (stream == null) { try { if (streamId.doubleValue() <= 0.0d) { streamId = streamConn.reserveStreamId(); } stream = streamConn.newPlaylistSubscriberStream(streamId); if (stream != null) { stream.setBroadcastStreamPublishName(name); stream.start(); created = true; } else { } } catch (Exception e) { } } if (stream instanceof ISubscriberStream) { ISubscriberStream subscriberStream = (ISubscriberStream) stream; IPlayItem item = simplePlayback.get() ? SimplePlayItem.build(name, start, length) : DynamicPlayItem.build(name, start, length); if (subscriberStream instanceof IPlaylistSubscriberStream) { IPlaylistSubscriberStream playlistStream = (IPlaylistSubscriberStream) subscriberStream; if (flushPlaylist) { playlistStream.removeAllItems(); } playlistStream.addItem(item); } else if (subscriberStream instanceof ISingleItemSubscriberStream) { ISingleItemSubscriberStream singleStream = (ISingleItemSubscriberStream) subscriberStream; singleStream.setPlayItem(item); } else { return; } try { subscriberStream.play(); } catch (IOException err) { } } } else { } }
关于安全方面的源码本章并不涉及,跳过play函数前面不重要的一些代码,假设从RTMPMinaConnection中取得的stream为null,下面就开始构造一个Stream了。
首先通过reserveStreamId函数随机分配一个streamId,该函数之前的章节分析过了。获得streamId之后,执行newPlaylistSubscriberStream函数创建一个PlaylistSubscriberStream,该函数定义在RTMPConnection中,
public IPlaylistSubscriberStream newPlaylistSubscriberStream(Number streamId) { if (isValidStreamId(streamId)) { PlaylistSubscriberStream pss = (PlaylistSubscriberStream) scope.getContext().getBean("playlistSubscriberStream"); customizeStream(streamId, pss); if (!registerStream(pss)) { pss = null; } return pss; } return null; }
newPlaylistSubscriberStream函数首先检查streamId的合法性,然后创建一个PlaylistSubscriberStream,接着调用customizeStream设置PlaylistSubscriberStream的基本信息,
private void customizeStream(Number streamId, AbstractClientStream stream) { Integer buffer = streamBuffers.get(streamId.doubleValue()); if (buffer != null) { stream.setClientBufferDuration(buffer); } stream.setName(createStreamName()); stream.setConnection(this); stream.setScope(this.getScope()); stream.setStreamId(streamId); }
注意这里PlaylistSubscriberStream的name是随机创建的。
回到RTMPConnection的newPlaylistSubscriberStream函数中,最后通过registerStream注册刚刚创建的PlaylistSubscriberStream,代码如下
private boolean registerStream(IClientStream stream) { if (streams.putIfAbsent(stream.getStreamId().doubleValue(), stream) == null) { usedStreams.incrementAndGet(); return true; } return false; }
回到play函数中,创建完PlaylistSubscriberStream后,执行setBroadcastStreamPublishName设置需要播放的流的名称,再往下就调用PlaylistSubscriberStream的start函数,代码如下,
public void start() { if (engine == null) { IScope scope = getScope(); if (scope != null) { IContext ctx = scope.getContext(); if (ctx.hasBean(ISchedulingService.BEAN_NAME)) { schedulingService = (ISchedulingService) ctx.getBean(ISchedulingService.BEAN_NAME); } else { schedulingService = (ISchedulingService) scope.getParent().getContext().getBean(ISchedulingService.BEAN_NAME); } IConsumerService consumerService = null; if (ctx.hasBean(IConsumerService.KEY)) { consumerService = (IConsumerService) ctx.getBean(IConsumerService.KEY); } else { consumerService = (IConsumerService) scope.getParent().getContext().getBean(IConsumerService.KEY); } IProviderService providerService = null; if (ctx.hasBean(IProviderService.BEAN_NAME)) { providerService = (IProviderService) ctx.getBean(IProviderService.BEAN_NAME); } else { providerService = (IProviderService) scope.getParent().getContext().getBean(IProviderService.BEAN_NAME); } engine = new PlayEngine.Builder(this, schedulingService, consumerService, providerService).build(); } else { } } engine.setBufferCheckInterval(bufferCheckInterval); engine.setUnderrunTrigger(underrunTrigger); engine.start(); onChange(StreamState.STARTED); }
PlaylistSubscriberStream的start函数创建了QuartzSchedulingService、ConsumerService和ProviderService,这三个Service定义在Spring的配置文件red5-common.xml中,如下所示
<bean id="providerService" class="org.red5.server.stream.ProviderService"/> <bean id="consumerService" class="org.red5.server.stream.ConsumerService"/> <bean id="schedulingService" class="org.red5.server.scheduling.QuartzSchedulingService"> <property name="configFile" value="${red5.root}/conf/quartz.properties"/> </bean>
接下来,根据刚刚创建的三个Service创建一个PlayEngine,对PlayEngine进行相应的设置后,就调用PlayEngine的start函数,代码如下,
public void start() { switch (subscriberStream.getState()) { case UNINIT: subscriberStream.setState(StreamState.STOPPED); if (msgOut == null) { msgOut = consumerService.getConsumerOutput(subscriberStream); msgOut.subscribe(this, null); } break; default: } }
这里的subscriberStream成员变量就是PlaylistSubscriberStream,初始化时其成员变量state为UNINIT,
start函数首先设置PlaylistSubscriberStream的state为STOPPED,然后调用ConsumerService的getConsumerOutput获得一个InMemoryPushPushPipe,然后调用其subscribe注册PlaylistSubscriberStream自身。getConsumerOutput和subscribe函数在《red5源码分析—10》中详细分析过了,这里就不往下看了。
回到play函数中,接下来创建了一个SimplePlayItem,并且调用addItem添加到PlaylistSubscriberStream中,最后调用PlaylistSubscriberStream的play函数,
public void play() throws IOException { int count = items.size(); if (count == 0) { return; } if (currentItemIndex == -1) { moveToNext(); } while (count-- > 0) { IPlayItem item = null; read.lock(); try { item = items.get(currentItemIndex); engine.play(item); break; } catch (StreamNotFoundException e) { moveToNext(); if (currentItemIndex == -1) { break; } item = items.get(currentItemIndex); } catch (IllegalStateException e) { break; } finally { read.unlock(); } } }
PlaylistSubscriberStream的play函数获取刚刚创建的SimplePlayItem,并调用PlayEngine的play函数,
public void play(IPlayItem item) throws StreamNotFoundException, IllegalStateException, IOException { play(item, true); } public void play(IPlayItem item, boolean withReset) throws StreamNotFoundException, IllegalStateException, IOException { switch (subscriberStream.getState()) { case STOPPED: if (msgIn != null) { msgIn.unsubscribe(this); msgIn = null; } break; default: throw new IllegalStateException("Cannot play from non-stopped state"); } int type = (int) (item.getStart() / 1000); IScope thisScope = subscriberStream.getScope(); final String itemName = item.getName(); IProviderService.INPUT_TYPE sourceType = providerService.lookupProviderInput(thisScope, itemName, type); boolean isPublishedStream = sourceType == IProviderService.INPUT_TYPE.LIVE; boolean isPublishedStreamWait = sourceType == IProviderService.INPUT_TYPE.LIVE_WAIT; boolean isFileStream = sourceType == IProviderService.INPUT_TYPE.VOD; boolean sendNotifications = true; switch (type) { case -2: if (isPublishedStream) { playDecision = 0; } else if (isFileStream) { playDecision = 1; } else if (isPublishedStreamWait) { playDecision = 2; } break; case -1: if (isPublishedStream) { playDecision = 0; } else { playDecision = 2; } break; default: if (isFileStream) { playDecision = 1; } break; } IMessage msg = null; currentItem = item; long itemLength = item.getLength(); switch (playDecision) { case 0: msgIn = providerService.getLiveProviderInput(thisScope, itemName, false); if (msgIn == null) { } else { videoFrameDropper.reset(IFrameDropper.SEND_KEYFRAMES_CHECK); if (msgIn instanceof IBroadcastScope) { IBroadcastStream stream = (IBroadcastStream) ((IBroadcastScope) msgIn).getClientBroadcastStream(); if (stream != null && stream.getCodecInfo() != null) { IVideoStreamCodec videoCodec = stream.getCodecInfo().getVideoCodec(); if (videoCodec != null) { if (withReset) { sendReset(); sendResetStatus(item); sendStartStatus(item); } sendNotifications = false; if (videoCodec.getNumInterframes() > 0 || videoCodec.getKeyframe() != null) { bufferedInterframeIdx = 0; videoFrameDropper.reset(IFrameDropper.SEND_ALL); } } } } if (msgIn != null) { msgIn.subscribe(this, null); playLive(); } else { } } break; case 2: ... break; case 1: ... break; default: } if (sendNotifications) { if (withReset) { sendReset(); sendResetStatus(item); } sendStartStatus(item); if (!withReset) { sendSwitchStatus(); } if (item instanceof DynamicPlayItem) { sendTransitionStatus(); } } if (msg != null) { sendMessage((RTMPMessage) msg); } subscriberStream.onChange(StreamState.PLAYING, currentItem, !pullMode); if (withReset) { long currentTime = System.currentTimeMillis(); playbackStart = currentTime - streamOffset; nextCheckBufferUnderrun = currentTime + bufferCheckInterval; if (currentItem.getLength() != 0) { ensurePullAndPushRunning(); } } }
playEngine函数首先对msgIn进行初始化,接着调用ProviderService的lookupProviderInput方法获取输入流,代码如下,
public INPUT_TYPE lookupProviderInput(IScope scope, String name, int type) { INPUT_TYPE result = INPUT_TYPE.NOT_FOUND; if (scope.getBasicScope(ScopeType.BROADCAST, name) != null) { result = INPUT_TYPE.LIVE; } else { result = INPUT_TYPE.VOD; File file = getStreamFile(scope, name); if (file == null) { if (type == -2) { result = INPUT_TYPE.LIVE_WAIT; } } } return result; }
参考《red5源码分析—10》,这里假设会获取到之前注册过的BroadcastScope,因此返回LIVE。
回到PlayEngine的play函数中,假设经过处理后的playDecision为0,下面来分析case为0的部分代码。首先会继续调用ProviderService的getLiveProviderInput方法获取一个BroadcastScope,
public IMessageInput getLiveProviderInput(IScope scope, String name, boolean needCreate) { IBroadcastScope broadcastScope = scope.getBroadcastScope(name); if (broadcastScope == null && needCreate) { synchronized (scope) { broadcastScope = scope.getBroadcastScope(name); if (broadcastScope == null) { broadcastScope = new BroadcastScope(scope, name); scope.addChildScope(broadcastScope); } } } return broadcastScope; }
值得注意的是如果原先没有注册过BroadcastScope,getLiveProviderInput方法会根据name创建一个。
回到play函数中,根据返回的BroadcastScope获取相应的参数之后,就通过sendReset、sendResetStatus和sendStartStatus向客户端发送消息,通知流即将开始了。三个方法最后都是通过doPushMessage发送消息,代码如下
private void doPushMessage(AbstractMessage message) { if (msgOut != null) { try { msgOut.pushMessage(message); if (message instanceof RTMPMessage) { IRTMPEvent body = ((RTMPMessage) message).getBody(); lastMessageTs = body.getTimestamp(); IoBuffer streamData = null; if (body instanceof IStreamData && (streamData = ((IStreamData<?>) body).getData()) != null) { bytesSent.addAndGet(streamData.limit()); } } } catch (IOException err) { } } else { } }
doPushMessage函数中msgOut就是InMemoryPushPushPipe,其pushMessage函数在《red5源码分析—10》中已经详细分析过了。
回到PlayEngine的play函数中,接下来调用BroadcastScope的subscribe函数注册自身,这也是play命令最重要的一件事情,因为将subscripe注册进BroadcastScope中之后,当有Provider(即流的源)向red5服务器发送数据时,才能根据这些注册信息将数据推送给客户端。
play函数接着调用playLive函数,代码如下,
private final void playLive() throws IOException { subscriberStream.setState(StreamState.PLAYING); streamOffset = 0; streamStartTS.set(-1); if (msgIn != null && msgOut != null) { IBroadcastStream stream = (IBroadcastStream) ((IBroadcastScope) msgIn).getClientBroadcastStream(); if (stream != null) { Notify metaData = stream.getMetaData(); if (metaData != null) { RTMPMessage metaMsg = RTMPMessage.build(metaData, 0); try { msgOut.pushMessage(metaMsg); } catch (IOException e) { } } else { } IStreamCodecInfo codecInfo = stream.getCodecInfo(); if (codecInfo instanceof StreamCodecInfo) { StreamCodecInfo info = (StreamCodecInfo) codecInfo; IVideoStreamCodec videoCodec = info.getVideoCodec(); if (videoCodec != null) { IoBuffer config = videoCodec.getDecoderConfiguration(); if (config != null) { VideoData conf = new VideoData(config.asReadOnlyBuffer()); RTMPMessage confMsg = RTMPMessage.build(conf); try { msgOut.pushMessage(confMsg); } finally { conf.release(); } } IoBuffer keyFrame = videoCodec.getKeyframe(); if (keyFrame != null) { VideoData video = new VideoData(keyFrame.asReadOnlyBuffer()); RTMPMessage videoMsg = RTMPMessage.build(video); try { msgOut.pushMessage(videoMsg); } finally { video.release(); } } } else { } IAudioStreamCodec audioCodec = info.getAudioCodec(); if (audioCodec != null) { IoBuffer config = audioCodec.getDecoderConfiguration(); if (config != null) { AudioData conf = new AudioData(config.asReadOnlyBuffer()); RTMPMessage confMsg = RTMPMessage.build(conf); try { msgOut.pushMessage(confMsg); } finally { conf.release(); } } } else { } } } } else { } }
playLive函数主要向客户端发送metadata、解码器配置等等,这里就不详细分析了。
PlayEngine的play函数剩下的代码和主线无关,这里就不往下分析了。
纵观red5服务器对整个play命令的处理,其实最重要的就是将客户端注册进对应管道的Consumer中。回顾《red5源码分析—12》中ClientBroadcastStream的dispatchEvent函数,其中会调用livePipe的pushMessage函数,这里的livePipe就是BroadcastScope中创建的InMemoryPushPushPipe,其pushMessage函数会遍历InMemoryPushPushPipe中所有注册的Consumer,并调用其pushMessage函数,因此,下面来看PlayEngine的pushMessage函数,
public void pushMessage(IPipe pipe, IMessage message) throws IOException { if (message instanceof RTMPMessage) { RTMPMessage rtmpMessage = (RTMPMessage) message; IRTMPEvent body = rtmpMessage.getBody(); if (body instanceof IStreamData) { if (subscriberStream.getState() == StreamState.PAUSED) { videoFrameDropper.dropPacket(rtmpMessage); return; } if (body instanceof VideoData) { if (msgIn instanceof IBroadcastScope) { IBroadcastStream stream = (IBroadcastStream) ((IBroadcastScope) msgIn).getClientBroadcastStream(); if (stream != null && stream.getCodecInfo() != null) { IVideoStreamCodec videoCodec = stream.getCodecInfo().getVideoCodec(); if (videoCodec != null && videoCodec.canDropFrames()) { if (!receiveVideo) { videoFrameDropper.dropPacket(rtmpMessage); return; } long pendingVideos = pendingVideoMessages(); if (!videoFrameDropper.canSendPacket(rtmpMessage, pendingVideos)) { return; } if (pendingVideos > 1) { numSequentialPendingVideoFrames++; } else { numSequentialPendingVideoFrames = 0; } if (pendingVideos > maxPendingVideoFramesThreshold || numSequentialPendingVideoFrames > maxSequentialPendingVideoFrames) { long now = System.currentTimeMillis(); if (bufferCheckInterval > 0 && now >= nextCheckBufferUnderrun) { sendInsufficientBandwidthStatus(currentItem); nextCheckBufferUnderrun = now + bufferCheckInterval; } videoFrameDropper.dropPacket(rtmpMessage); return; } if (bufferedInterframeIdx > -1) { IVideoStreamCodec.FrameData fd = videoCodec.getInterframe(bufferedInterframeIdx++); if (fd != null) { VideoData interframe = new VideoData(fd.getFrame()); interframe.setTimestamp(body.getTimestamp()); rtmpMessage = RTMPMessage.build(interframe); } else { bufferedInterframeIdx = 0; } } } } } } else if (body instanceof AudioData) { if (!receiveAudio && sendBlankAudio) { sendBlankAudio = false; body = new AudioData(); if (lastMessageTs > 0) { body.setTimestamp(lastMessageTs); } else { body.setTimestamp(0); } rtmpMessage = RTMPMessage.build(body); } else if (!receiveAudio) { return; } } sendMessage(rtmpMessage); } else { } } else if (message instanceof ResetMessage) { sendReset(); } else { msgOut.pushMessage(message); } }
PlayEngine的pushMessage函数其实很简单,最重要的是最下边的sendMessage函数,剩下的代码就是在根据网络情况判断是否应该发送该数据。sendMessage函数最终会调用doPushMessage函数发送数据,这里就不往下看了。
receiveVideo命令
red5服务器接收到receiveVideo命令后,最终会调用StreamService的receiveVideo方法,代码如下,
public void receiveVideo(boolean receive) { IConnection conn = Red5.getConnectionLocal(); if (conn instanceof IStreamCapableConnection) { IStreamCapableConnection streamConn = (IStreamCapableConnection) conn; Number streamId = conn.getStreamId(); IClientStream stream = streamConn.getStreamById(streamId); if (stream != null && stream instanceof ISubscriberStream) { ISubscriberStream subscriberStream = (ISubscriberStream) stream; subscriberStream.receiveVideo(receive); } } }
receiveVideo函数其实就是执行PlaylistSubscriberStream的receiveVideo函数,
public void receiveVideo(boolean receive) { if (engine != null) { boolean receiveVideo = engine.receiveVideo(receive); if (!receiveVideo && receive) { seekToCurrentPlayback(); } } else { } }
receiveVideo函数最重要的就是调用playEngine的receiveVideo函数进行相应地设置,表示该playEngine可以或者拒绝接收数据。
public boolean receiveVideo(boolean receive) { boolean oldValue = receiveVideo; if (receiveVideo != receive) { receiveVideo = receive; } return oldValue; }
receiveAudio命令和receiveVideo命令类似,本章就不往下分析了。
- red5源码分析---13
- red5源码分析(转)
- red5源码分析
- red5源码分析---1
- red5源码分析---2
- red5源码分析---3
- red5源码分析---4
- red5源码分析---5
- red5源码分析---6
- red5源码分析---7
- red5源码分析---8
- red5源码分析---9
- red5源码分析---10
- red5源码分析---11
- red5源码分析---12
- Red5源码研究一
- Red5源码研究一
- red5-server源码
- implements和extends的区别 延伸到Thread和runable的区别
- [从头学数学] 第210节 带着计算机去高考(二)
- 第11周项目2—储存班长信息的学生类
- 值“MD_DynamicRelease”不匹配值“MDd_DynamicDebug”的问题
- 1.0 mysql的连接
- red5源码分析---13
- linux内核文件
- Linux进程间通信_IPC机制的深入理解2
- android float小数点内2位
- 数组——container-with-most-water和trapping-rain-water
- linux模块编程
- Window7下安装openssl完整版(亲测实现)
- 不同浏览器兼容问题—— input 添加required属性 firefox下输入框为红色 +禁止中文输入
- intellij idea 搭建scala环境和一些问题