Java tutorial
package org.red5.server.net.rtmp.codec; /* * 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 java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.apache.mina.core.buffer.IoBuffer; import org.red5.server.api.IConnection.Encoding; import org.red5.server.net.protocol.ProtocolState; import org.red5.server.net.rtmp.message.Header; import org.red5.server.net.rtmp.message.Packet; import org.springframework.core.style.ToStringCreator; /** * RTMP is the RTMP protocol state representation. */ public class RTMP extends ProtocolState { /** * Connect state. */ public static final byte STATE_CONNECT = 0x00; /** * Handshake state. Server sends handshake request to client right after connection estabilished. */ public static final byte STATE_HANDSHAKE = 0x01; /** * Connected. */ public static final byte STATE_CONNECTED = 0x02; /** * Error. */ public static final byte STATE_ERROR = 0x03; /** * Disconnected. */ public static final byte STATE_DISCONNECTED = 0x04; /** * Sent the connect message to origin. */ public static final byte STATE_EDGE_CONNECT_ORIGIN_SENT = 0x11; /** * Forwarded client's connect call to origin. */ public static final byte STATE_ORIGIN_CONNECT_FORWARDED = 0x12; /** * Edge is disconnecting, waiting Origin close connection. */ public static final byte STATE_EDGE_DISCONNECTING = 0x13; /** * Client mode. */ public static final boolean MODE_CLIENT = true; /** * Server mode. */ public static final boolean MODE_SERVER = false; /** * Default chunk size. Packets are read and written chunk-by-chunk. */ public static final int DEFAULT_CHUNK_SIZE = 128; /** * RTMP state. */ private byte state = STATE_CONNECT; /** * Server mode by default. */ private volatile boolean mode = MODE_SERVER; /** * Debug flag. */ private boolean debug; /** * Last read channel. */ private int lastReadChannel = 0x00; /** * Last write channel. */ private int lastWriteChannel = 0x00; /** * Read headers, keyed by channel id. */ private final Map<Integer, Header> readHeaders = new HashMap<Integer, Header>(); /** * Write headers, keyed by channel id. */ private final Map<Integer, Header> writeHeaders = new HashMap<Integer, Header>(); /** * Headers actually used for a packet, keyed by channel id. */ private final Map<Integer, Header> readPacketHeaders = new HashMap<Integer, Header>(); /** * Read packets, keyed by channel id. */ private final Map<Integer, Packet> readPackets = new HashMap<Integer, Packet>(); /** * Written packets, keyed by channel id. */ private final Map<Integer, Packet> writePackets = new HashMap<Integer, Packet>(); /** * Written timestamps */ private final Map<Integer, Integer> writeTimestamps = new HashMap<Integer, Integer>(); /** * Class for mapping between clock time and stream time for live streams * @author aclarke * */ static class LiveTimestampMapping { private final long clockStartTime; private final long streamStartTime; private boolean keyFrameNeeded; private long lastStreamTime; public LiveTimestampMapping(long clockStartTime, long streamStartTime) { this.clockStartTime = clockStartTime; this.streamStartTime = streamStartTime; this.keyFrameNeeded = true; // Always start with a key frame this.lastStreamTime = streamStartTime; } public long getStreamStartTime() { return streamStartTime; } public long getClockStartTime() { return clockStartTime; } public void setKeyFrameNeeded(boolean keyFrameNeeded) { this.keyFrameNeeded = keyFrameNeeded; } public boolean isKeyFrameNeeded() { return keyFrameNeeded; } public long getLastStreamTime() { return lastStreamTime; } public void setLastStreamTime(long lastStreamTime) { this.lastStreamTime = lastStreamTime; } } /** * Mapping between channel and the last clock to stream mapping */ private final Map<Integer, LiveTimestampMapping> liveTimestamps = new HashMap<Integer, LiveTimestampMapping>(); /** * Read chunk size. Packets are read and written chunk-by-chunk. */ private int readChunkSize = DEFAULT_CHUNK_SIZE; /** * Write chunk size. Packets are read and written chunk-by-chunk. */ private int writeChunkSize = DEFAULT_CHUNK_SIZE; /** * Encoding type for objects. */ private Encoding encoding = Encoding.AMF0; /** * Handshake as sent to the client. */ private byte[] handshake; /** * Creates RTMP object with initial mode. * * @param mode Initial mode */ public RTMP(boolean mode) { this.mode = mode; } /** * Return current mode. * * @return Current mode */ public boolean getMode() { return mode; } /** * Getter for debug. * * @return Debug state */ public boolean isDebug() { return debug; } /** * Setter for debug. * * @param debug Debug flag new value */ public void setDebug(boolean debug) { this.debug = debug; } /** * Return current state. * * @return State */ public byte getState() { return state; } /** * Releases number of packets. * * @param packets Packets to release */ private void freePackets(Map<Integer, Packet> packets) { for (Packet packet : packets.values()) { if (packet != null && packet.getData() != null) { packet.getData().free(); packet.setData(null); } } packets.clear(); } /** * Setter for state. * * @param state New state */ public void setState(byte state) { this.state = state; if (state == STATE_DISCONNECTED) { // Free temporary packets freePackets(readPackets); freePackets(writePackets); readHeaders.clear(); writeHeaders.clear(); } } /** * Setter for last read header. * * @param channelId Channel id * @param header Header */ public void setLastReadHeader(int channelId, Header header) { lastReadChannel = channelId; readHeaders.put(channelId, header); } /** * Return last read header for channel. * * @param channelId Channel id * @return Last read header */ public Header getLastReadHeader(int channelId) { return readHeaders.get(channelId); } /** * Setter for last written header. * * @param channelId Channel id * @param header Header */ public void setLastWriteHeader(int channelId, Header header) { lastWriteChannel = channelId; writeHeaders.put(channelId, header); } /** * Return last written header for channel. * * @param channelId Channel id * @return Last written header */ public Header getLastWriteHeader(int channelId) { return writeHeaders.get(channelId); } /** * Setter for last read packet. * * @param channelId Channel id * @param packet Packet */ public void setLastReadPacket(int channelId, Packet packet) { Packet prevPacket = readPackets.put(channelId, packet); if (prevPacket != null && prevPacket.getData() != null) { prevPacket.getData().free(); prevPacket.setData(null); } } /** * Return last read packet for channel. * * @param channelId Channel id * @return Last read packet for that channel */ public Packet getLastReadPacket(int channelId) { return readPackets.get(channelId); } /** * Setter for last written packet. * * @param channelId Channel id * @param packet Last written packet */ public void setLastWritePacket(int channelId, Packet packet) { // Disabled to help GC because we currently don't use the write packets /* Packet prevPacket = writePackets.put(channelId, packet); if (prevPacket != null && prevPacket.getData() != null) { prevPacket.getData().release(); prevPacket.setData(null); } */ } /** * Return packet that has been written last. * * @param channelId Channel id * @return Packet that has been written last */ public Packet getLastWritePacket(int channelId) { return writePackets.get(channelId); } /** * Return channel being read last. * * @return Last read channel */ public int getLastReadChannel() { return lastReadChannel; } /** * Getter for channel being written last. * * @return Last write channel */ public int getLastWriteChannel() { return lastWriteChannel; } /** * Getter for write chunk size. Data is being read chunk-by-chunk. * * @return Read chunk size */ public int getReadChunkSize() { return readChunkSize; } /** * Setter for read chunk size. Data is being read chunk-by-chunk. * * @param readChunkSize Value to set for property 'readChunkSize'. */ public void setReadChunkSize(int readChunkSize) { this.readChunkSize = readChunkSize; } /** * Getter for write chunk size. Data is being written chunk-by-chunk. * * @return Write chunk size */ public int getWriteChunkSize() { return writeChunkSize; } /** * Setter for write chunk size. * * @param writeChunkSize Write chunk size */ public void setWriteChunkSize(int writeChunkSize) { this.writeChunkSize = writeChunkSize; } /** * Getter for encoding version. * * @return Encoding version */ public Encoding getEncoding() { return encoding; } /** * Setter for encoding version. * * @param encoding Encoding version */ public void setEncoding(Encoding encoding) { this.encoding = encoding; } /** * Store the handshake sent to the client. * * @param data Handshake data * @param start where handshake data starts in data * @param length Length of handshake to store */ public void setHandshake(IoBuffer data, int start, int length) { handshake = new byte[length]; int old = data.position(); data.position(start); data.get(handshake); data.position(old); } /** * Check if the handshake reply received from a client contains valid data. * * @param data data * @param start where handshake data starts in data * @param length length * @return true on success; false otherwise */ public boolean validateHandshakeReply(IoBuffer data, int start, int length) { if (handshake == null || length != handshake.length) { return false; } byte[] reply = new byte[length]; int old = data.position(); data.position(start); data.get(reply); data.position(old); return Arrays.equals(reply, handshake); } @Override public String toString() { ToStringCreator tsc = new ToStringCreator(this); return tsc.toString(); } public void setLastFullTimestampWritten(int channelId, int timer) { writeTimestamps.put(channelId, timer); } public Integer getLastFullTimestampWritten(int channelId) { return writeTimestamps.get(channelId); } public void setLastReadPacketHeader(int channelId, Header header) { readPacketHeaders.put(channelId, header); } public Header getLastReadPacketHeader(int channelId) { return readPacketHeaders.get(channelId); } LiveTimestampMapping getLastTimestampMapping(int channelId) { return liveTimestamps.get(channelId); } void setLastTimestampMapping(int channelId, LiveTimestampMapping mapping) { liveTimestamps.put(channelId, mapping); } }