package org.red5.server.stream.consumer; /* * RED5 Open Source Flash Server - http://www.osflash.org/red5 * * Copyright (c) 2006-2009 by respective authors (see below). All rights reserved. * * This library is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any later * version. * * This library is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along * with this library; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ import org.red5.server.api.IBWControllable; import org.red5.server.api.IBandwidthConfigure; import org.red5.server.api.IConnectionBWConfig; import org.red5.server.api.stream.IClientStream; import org.red5.server.messaging.IMessage; import org.red5.server.messaging.IMessageComponent; import org.red5.server.messaging.IPipe; import org.red5.server.messaging.IPipeConnectionListener; import org.red5.server.messaging.IPushableConsumer; import org.red5.server.messaging.OOBControlMessage; import org.red5.server.messaging.PipeConnectionEvent; import org.red5.server.net.rtmp.Channel; import org.red5.server.net.rtmp.RTMPConnection; import org.red5.server.net.rtmp.event.AudioData; import org.red5.server.net.rtmp.event.BytesRead; import org.red5.server.net.rtmp.event.ChunkSize; import org.red5.server.net.rtmp.event.FlexStreamSend; import org.red5.server.net.rtmp.event.IRTMPEvent; import org.red5.server.net.rtmp.event.Notify; import org.red5.server.net.rtmp.event.Ping; import org.red5.server.net.rtmp.event.VideoData; import org.red5.server.net.rtmp.message.Constants; import org.red5.server.net.rtmp.message.Header; import org.red5.server.stream.StreamTracker; import org.red5.server.stream.message.RTMPMessage; import org.red5.server.stream.message.ResetMessage; import org.red5.server.stream.message.StatusMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * RTMP connection consumer. */ public class ConnectionConsumer implements IPushableConsumer, IPipeConnectionListener { /** * Logger */ private static final Logger log = LoggerFactory.getLogger(ConnectionConsumer.class); /** * Connection consumer class name */ public static final String KEY = ConnectionConsumer.class.getName(); /** * Connection object */ private RTMPConnection conn; /** * Video channel */ private Channel video; /** * Audio channel */ private Channel audio; /** * Data channel */ private Channel data; /** * Chunk size. Packets are sent chunk-by-chunk. */ private int chunkSize = -1; /** * Stream tracker */ private StreamTracker streamTracker; /** * Create rtmp connection consumer for given connection and channels * @param conn RTMP connection * @param videoChannel Video channel * @param audioChannel Audio channel * @param dataChannel Data channel */ public ConnectionConsumer(RTMPConnection conn, int videoChannel, int audioChannel, int dataChannel) { this.conn = conn; this.video = conn.getChannel(videoChannel); this.audio = conn.getChannel(audioChannel); this.data = conn.getChannel(dataChannel); streamTracker = new StreamTracker(); } /** {@inheritDoc} */ public void pushMessage(IPipe pipe, IMessage message) { if (message instanceof ResetMessage) { streamTracker.reset(); } else if (message instanceof StatusMessage) { StatusMessage statusMsg = (StatusMessage) message; data.sendStatus(statusMsg.getBody()); } else if (message instanceof RTMPMessage) { RTMPMessage rtmpMsg = (RTMPMessage) message; IRTMPEvent msg = rtmpMsg.getBody(); Header header = new Header(); int timestamp = streamTracker.add(msg); if (timestamp < 0) { log.warn("Skipping message with negative timestamp."); return; } header.setTimerRelative(streamTracker.isRelative()); header.setTimer(timestamp); switch (msg.getDataType()) { case Constants.TYPE_STREAM_METADATA: Notify notify = new Notify(((Notify) msg).getData() .asReadOnlyBuffer()); notify.setHeader(header); notify.setTimestamp(header.getTimer()); data.write(notify); break; case Constants.TYPE_FLEX_STREAM_SEND: // TODO: okay to send this also to AMF0 clients? FlexStreamSend send = new FlexStreamSend(((Notify) msg).getData() .asReadOnlyBuffer()); send.setHeader(header); send.setTimestamp(header.getTimer()); data.write(send); break; case Constants.TYPE_VIDEO_DATA: VideoData videoData = new VideoData(((VideoData) msg) .getData().asReadOnlyBuffer()); videoData.setHeader(header); videoData.setTimestamp(header.getTimer()); video.write(videoData); break; case Constants.TYPE_AUDIO_DATA: AudioData audioData = new AudioData(((AudioData) msg) .getData().asReadOnlyBuffer()); audioData.setHeader(header); audioData.setTimestamp(header.getTimer()); audio.write(audioData); break; case Constants.TYPE_PING: Ping ping = new Ping(((Ping) msg).getValue1(), ((Ping) msg) .getValue2(), ((Ping) msg).getValue3(), ((Ping) msg).getValue4()); header.setTimerRelative(false); header.setTimer(0); ping.setHeader(header); ping.setTimestamp(header.getTimer()); conn.ping(ping); break; case Constants.TYPE_BYTES_READ: BytesRead bytesRead = new BytesRead(((BytesRead) msg) .getBytesRead()); header.setTimerRelative(false); header.setTimer(0); bytesRead.setHeader(header); bytesRead.setTimestamp(header.getTimer()); conn.getChannel((byte) 2).write(bytesRead); break; default: data.write(msg); break; } } } /** {@inheritDoc} */ public void onPipeConnectionEvent(PipeConnectionEvent event) { switch (event.getType()) { case PipeConnectionEvent.PROVIDER_DISCONNECT: // XXX should put the channel release code in ConsumerService conn.closeChannel(this.video.getId()); conn.closeChannel(this.audio.getId()); conn.closeChannel(this.data.getId()); break; default: break; } } /** {@inheritDoc} */ public void onOOBControlMessage(IMessageComponent source, IPipe pipe, OOBControlMessage oobCtrlMsg) { if (!"ConnectionConsumer".equals(oobCtrlMsg.getTarget())) { return; } if ("pendingCount".equals(oobCtrlMsg.getServiceName())) { oobCtrlMsg.setResult(conn.getPendingMessages()); } else if ("pendingVideoCount".equals(oobCtrlMsg.getServiceName())) { IClientStream stream = conn.getStreamByChannelId(video.getId()); if (stream != null) { oobCtrlMsg.setResult(conn.getPendingVideoMessages(stream .getStreamId())); } else { oobCtrlMsg.setResult((long) 0); } } else if ("writeDelta".equals(oobCtrlMsg.getServiceName())) { long maxStream = 0; IBWControllable bwControllable = conn; // Search FC containing valid BWC while (bwControllable != null && bwControllable.getBandwidthConfigure() == null) { bwControllable = bwControllable.getParentBWControllable(); } if (bwControllable != null && bwControllable.getBandwidthConfigure() != null) { IBandwidthConfigure bwc = bwControllable.getBandwidthConfigure(); if (bwc instanceof IConnectionBWConfig) { maxStream = ((IConnectionBWConfig) bwc).getDownstreamBandwidth() / 8; } } if (maxStream <= 0) { // Use default value // TODO: this should be configured somewhere and sent to the client when connecting maxStream = 120*1024; } // Return the current delta between sent bytes and bytes the client // reported to have received, and the interval the client should use // for generating BytesRead messages (half of the allowed bandwidth). oobCtrlMsg.setResult(new Long[]{conn.getWrittenBytes() - conn.getClientBytesRead(), maxStream / 2}); } else if ("chunkSize".equals(oobCtrlMsg.getServiceName())) { int newSize = (Integer) oobCtrlMsg.getServiceParamMap().get( "chunkSize"); if (newSize != chunkSize) { chunkSize = newSize; ChunkSize chunkSizeMsg = new ChunkSize(chunkSize); conn.getChannel((byte) 2).write(chunkSizeMsg); } } } }