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命令类似,本章就不往下分析了。