org.red5.server.net.rtmp.codec.RTMPProtocolEncoder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android-rmtp-client Show documentation
Show all versions of android-rmtp-client Show documentation
A standalone RTMP client library ported from the Red5 project
The newest version!
package org.red5.server.net.rtmp.codec;
/*
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright (c) 2006-2010 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.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.mina.core.buffer.IoBuffer;
import org.red5.io.object.Output;
import org.red5.io.object.Serializer;
import org.red5.io.utils.BufferUtils;
import org.red5.server.IConnection;
import org.red5.server.Red5;
import org.red5.server.IConnection.Encoding;
//import org.red5.server.api.stream.IClientStream;
//import org.red5.server.exception.ClientDetailsException;
import org.red5.server.net.ProtocolState;
//import org.red5.server.net.rtmp.RTMPConnection;
import org.red5.server.net.rtmp.RTMPUtils;
import org.red5.server.net.rtmp.codec.RTMP.LiveTimestampMapping;
import org.red5.server.net.rtmp.event.Aggregate;
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.ClientBW;
import org.red5.server.net.rtmp.event.FlexMessage;
import org.red5.server.net.rtmp.event.FlexStreamSend;
import org.red5.server.net.rtmp.event.IRTMPEvent;
import org.red5.server.net.rtmp.event.Invoke;
import org.red5.server.net.rtmp.event.Notify;
import org.red5.server.net.rtmp.event.Ping;
import org.red5.server.net.rtmp.event.ServerBW;
import org.red5.server.net.rtmp.event.Unknown;
import org.red5.server.net.rtmp.event.VideoData;
import org.red5.server.net.rtmp.event.VideoData.FrameType;
import org.red5.server.net.rtmp.message.Constants;
import org.red5.server.net.rtmp.message.Header;
import org.red5.server.net.rtmp.message.Packet;
import org.red5.server.net.rtmp.message.SharedObjectTypeMapping;
import org.red5.server.net.rtmp.status.Status;
import org.red5.server.net.rtmp.status.StatusCodes;
import org.red5.server.net.rtmp.status.StatusObject;
import org.red5.server.service.Call;
import org.red5.server.service.IPendingServiceCall;
import org.red5.server.service.IServiceCall;
import org.red5.server.so.ISharedObjectEvent;
import org.red5.server.so.ISharedObjectMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* RTMP protocol encoder encodes RTMP messages and packets to byte buffers.
*/
public class RTMPProtocolEncoder implements Constants, IEventEncoder {
/**
* Logger.
*/
protected static Logger log = LoggerFactory.getLogger(RTMPProtocolEncoder.class);
/**
* Serializer object.
*/
private Serializer serializer;
/**
* Tolerance (in milliseconds) for late media on streams. A set of levels based on this
* value will be determined.
*/
private long baseTolerance = 15000;
/**
* Middle tardiness level, between base and this value disposable frames
* will be dropped. Between this and highest value regular interframes
* will be dropped.
*/
private long midTolerance = baseTolerance + (long) (baseTolerance * 0.3);
/**
* Highest tardiness level before dropping key frames
*/
private long highestTolerance = baseTolerance + (long) (baseTolerance * 0.6);
/**
* Indicates if we should drop live packets with future timestamp
* (i.e, when publisher bandwidth is limited) - EXPERIMENTAL
* */
private boolean dropLiveFuture = false;
/**
* Encodes object with given protocol state to byte buffer
*
* @param state Protocol state
* @param message Object to encode
* @return IoBuffer with encoded data
* @throws Exception Any decoding exception
*/
public IoBuffer encode(ProtocolState state, Object message) throws Exception {
try {
final RTMP rtmp = (RTMP) state;
if (message instanceof IoBuffer) {
return (IoBuffer) message;
} else {
return encodePacket(rtmp, (Packet) message);
}
} catch (RuntimeException e) {
log.error("Error encoding object: ", e);
}
return null;
}
/**
* Encode packet.
*
* @param rtmp RTMP protocol state
* @param packet RTMP packet
* @return Encoded data
*/
public IoBuffer encodePacket(RTMP rtmp, Packet packet) {
IoBuffer out = null;
IoBuffer data = null;
final Header header = packet.getHeader();
final int channelId = header.getChannelId();
// log.debug("Channel id: {}", channelId);
final IRTMPEvent message = packet.getMessage();
if (message instanceof ChunkSize) {
ChunkSize chunkSizeMsg = (ChunkSize) message;
rtmp.setWriteChunkSize(chunkSizeMsg.getSize());
}
//normally the message is expected not to be dropped
if (!dropMessage(rtmp, channelId, message)) {
data = encodeMessage(rtmp, header, message);
if (data != null) {
if (data.position() != 0) {
data.flip();
} else {
data.rewind();
}
header.setSize(data.limit());
Header lastHeader = rtmp.getLastWriteHeader(channelId);
int headerSize = calculateHeaderSize(rtmp, header, lastHeader);
rtmp.setLastWriteHeader(channelId, header);
rtmp.setLastWritePacket(channelId, packet);
int chunkSize = rtmp.getWriteChunkSize();
int chunkHeaderSize = 1;
if (header.getChannelId() > 320) {
chunkHeaderSize = 3;
} else if (header.getChannelId() > 63) {
chunkHeaderSize = 2;
}
int numChunks = (int) Math.ceil(header.getSize() / (float) chunkSize);
int bufSize = header.getSize() + headerSize + (numChunks > 0 ? (numChunks - 1) * chunkHeaderSize : 0);
out = IoBuffer.allocate(bufSize, false);
encodeHeader(rtmp, header, lastHeader, out);
if (numChunks == 1) {
// we can do it with a single copy
BufferUtils.put(out, data, out.remaining());
} else {
for (int i = 0; i < numChunks - 1; i++) {
BufferUtils.put(out, data, chunkSize);
RTMPUtils.encodeHeaderByte(out, HEADER_CONTINUE, header.getChannelId());
}
BufferUtils.put(out, data, out.remaining());
}
data.free();
out.flip();
data = null;
}
}
message.release();
return out;
}
/**
* Determine if this message should be dropped for lateness. Live publish data
* does not come through this section, only outgoing data does.
*
* - determine latency between server and client using ping
* - ping timestamp is unsigned int (4 bytes) and is set from value on sender
*
* 1st drop disposable frames - lowest mark
* 2nd drop interframes - middle
* 3rd drop key frames - high mark
*
* @param rtmp the protocol state
* @param channelId the channel ID
* @param message the message
* @return true to drop; false to send
*/
protected boolean dropMessage(RTMP rtmp, int channelId, IRTMPEvent message) {
//whether or not the packet will be dropped
boolean drop = false;
// we only drop in server mode
if (rtmp.getMode() == RTMP.MODE_SERVER) {
//whether or not the packet is video data
boolean isVideo = false;
if (message instanceof Ping) {
final Ping pingMessage = (Ping) message;
if (pingMessage.getEventType() == Ping.STREAM_PLAYBUFFER_CLEAR) {
// client buffer cleared, make sure to reset timestamps for this stream
final int channel = (4 + ((pingMessage.getValue2() - 1) * 5));
rtmp.setLastTimestampMapping(channel, null);
rtmp.setLastTimestampMapping(channel+1, null);
rtmp.setLastTimestampMapping(channel+2, null);
}
// never drop pings
return false;
}
//we only drop audio or video data
if ((isVideo = message instanceof VideoData) || message instanceof AudioData) {
if (message.getTimestamp() == 0) {
// never drop initial packages, also this could be the first packet after
// MP4 seeking and therefore mess with the timestamp mapping
return false;
}
//determine working type
boolean isLive = message.getSourceType() == Constants.SOURCE_TYPE_LIVE;
// log.trace("Connection type: {}", (isLive ? "Live" : "VOD"));
long timestamp = (message.getTimestamp() & 0xFFFFFFFFL);
LiveTimestampMapping mapping = rtmp.getLastTimestampMapping(channelId);
// just get the current time ONCE per packet
long now = System.currentTimeMillis();
if (mapping == null || timestamp < mapping.getLastStreamTime()) {
log.debug("Resetting clock time ({}) to stream time ({})", now, timestamp);
// either first time through, or time stamps were reset
mapping = new LiveTimestampMapping(now, timestamp);
rtmp.setLastTimestampMapping(channelId, mapping);
}
mapping.setLastStreamTime(timestamp);
long clockTimeOfMessage = mapping.getClockStartTime() + timestamp - mapping.getStreamStartTime();
//determine tardiness / how late it is
long tardiness = clockTimeOfMessage - now;
//TDJ: EXPERIMENTAL dropping for LIVE packets in future (default false)
if (isLive && dropLiveFuture) {
tardiness = Math.abs(tardiness);
}
//subtract the ping time / latency from the tardiness value
IConnection conn = Red5.getConnectionLocal();
// log.debug("Connection: {}", conn);
if (conn != null) {
// log.debug("Last ping time for connection: {}", conn.getLastPingTime());
tardiness -= conn.getLastPingTime();
//subtract the buffer time
// RTMPConnection rtmpConn = (RTMPConnection) conn;
// int streamId = rtmpConn.getStreamIdForChannel(channelId);
// IClientStream stream = rtmpConn.getStreamById(streamId);
// if (stream != null) {
// int clientBufferDuration = stream.getClientBufferDuration();
// if (clientBufferDuration > 0) {
// //two times the buffer duration seems to work best with vod
// if (!isLive) {
// tardiness -= clientBufferDuration * 2;
// } else {
// tardiness -= clientBufferDuration;
// }
// }
//// log.debug("Client buffer duration: {}", clientBufferDuration);
// }
} else {
log.debug("Connection is null");
}
//TODO: how should we differ handling based on live or vod?
//TODO: if we are VOD do we "pause" the provider when we are consistently late?
// log.debug("Packet timestamp: {}; tardiness: {}; now: {}; message clock time: {}, dropLiveFuture: {}", new Object[] { timestamp, tardiness, now, clockTimeOfMessage,
// dropLiveFuture });
//anything coming in less than the base will be allowed to pass, it will not be
//dropped or manipulated
if (tardiness < baseTolerance) {
//frame is below lowest bounds, let it go
} else if (tardiness > highestTolerance) {
//frame is really late, drop it no matter what type
log.debug("Dropping late message: {}", message);
//if we're working with video, indicate that we will need a key frame to proceed
if (isVideo) {
mapping.setKeyFrameNeeded(true);
}
//drop it
drop = true;
} else {
if (isVideo) {
VideoData video = (VideoData) message;
if (video.getFrameType() == FrameType.KEYFRAME) {
//if its a key frame the inter and disposible checks can be skipped
// log.debug("Resuming stream with key frame; message: {}", message);
mapping.setKeyFrameNeeded(false);
} else if (tardiness >= baseTolerance && tardiness < midTolerance) {
//drop disposable frames
if (video.getFrameType() == FrameType.DISPOSABLE_INTERFRAME) {
log.debug("Dropping disposible frame; message: {}", message);
drop = true;
}
} else if (tardiness >= midTolerance && tardiness <= highestTolerance) {
//drop inter-frames and disposable frames
log.debug("Dropping disposible or inter frame; message: {}", message);
drop = true;
}
}
}
}
log.debug("Drop data: {}", drop);
}
return drop;
}
/**
* Determine type of header to use.
*
* @param rtmp The protocol state
* @param header RTMP message header
* @param lastHeader Previous header
* @return Header type to use.
*/
private byte getHeaderType(final RTMP rtmp, final Header header, final Header lastHeader) {
if (lastHeader == null) {
return HEADER_NEW;
}
final Integer lastFullTs = rtmp.getLastFullTimestampWritten(header.getChannelId());
if (lastFullTs == null) {
return HEADER_NEW;
}
final byte headerType;
final long diff = RTMPUtils.diffTimestamps(header.getTimer(), lastHeader.getTimer());
final long timeSinceFullTs = RTMPUtils.diffTimestamps(header.getTimer(), lastFullTs);
if (header.getStreamId() != lastHeader.getStreamId() || diff < 0 || timeSinceFullTs >= 250) {
// New header mark if header for another stream
headerType = HEADER_NEW;
} else if (header.getSize() != lastHeader.getSize() || header.getDataType() != lastHeader.getDataType()) {
// Same source header if last header data type or size differ
headerType = HEADER_SAME_SOURCE;
} else if (header.getTimer() != lastHeader.getTimer() + lastHeader.getTimerDelta()) {
// Timer change marker if there's time gap between header time stamps
headerType = HEADER_TIMER_CHANGE;
} else {
// Continue encoding
headerType = HEADER_CONTINUE;
}
return headerType;
}
/**
* Calculate number of bytes necessary to encode the header.
*
* @param rtmp The protocol state
* @param header RTMP message header
* @param lastHeader Previous header
* @return Calculated size
*/
private int calculateHeaderSize(final RTMP rtmp, final Header header, final Header lastHeader) {
final byte headerType = getHeaderType(rtmp, header, lastHeader);
int channelIdAdd = 0;
int channelId = header.getChannelId();
if (channelId > 320) {
channelIdAdd = 2;
} else if (channelId > 63) {
channelIdAdd = 1;
}
return RTMPUtils.getHeaderLength(headerType) + channelIdAdd;
}
/**
* Encode RTMP header.
* @param rtmp The protocol state
* @param header RTMP message header
* @param lastHeader Previous header
* @return Encoded header data
*/
public IoBuffer encodeHeader(final RTMP rtmp, final Header header, final Header lastHeader) {
final IoBuffer result = IoBuffer.allocate(calculateHeaderSize(rtmp, header, lastHeader));
encodeHeader(rtmp, header, lastHeader, result);
return result;
}
/**
* Encode RTMP header into given IoBuffer.
*
* @param rtmp The protocol state
* @param header RTMP message header
* @param lastHeader Previous header
* @param buf Buffer to write encoded header to
*/
public void encodeHeader(final RTMP rtmp, final Header header, final Header lastHeader, final IoBuffer buf) {
final byte headerType = getHeaderType(rtmp, header, lastHeader);
RTMPUtils.encodeHeaderByte(buf, headerType, header.getChannelId());
final int timer;
switch (headerType) {
case HEADER_NEW:
timer = header.getTimer();
if (timer < 0 || timer >= 0xffffff) {
RTMPUtils.writeMediumInt(buf, 0xffffff);
} else {
RTMPUtils.writeMediumInt(buf, timer);
}
RTMPUtils.writeMediumInt(buf, header.getSize());
buf.put(header.getDataType());
RTMPUtils.writeReverseInt(buf, header.getStreamId());
if (timer < 0 || timer >= 0xffffff) {
buf.putInt(timer);
}
header.setTimerBase(timer);
header.setTimerDelta(0);
rtmp.setLastFullTimestampWritten(header.getChannelId(), timer);
break;
case HEADER_SAME_SOURCE:
timer = (int) RTMPUtils.diffTimestamps(header.getTimer(), lastHeader.getTimer());
if (timer < 0 || timer >= 0xffffff) {
RTMPUtils.writeMediumInt(buf, 0xffffff);
} else {
RTMPUtils.writeMediumInt(buf, timer);
}
RTMPUtils.writeMediumInt(buf, header.getSize());
buf.put(header.getDataType());
if (timer < 0 || timer >= 0xffffff) {
buf.putInt(timer);
}
header.setTimerBase(header.getTimer() - timer);
header.setTimerDelta(timer);
break;
case HEADER_TIMER_CHANGE:
timer = (int) RTMPUtils.diffTimestamps(header.getTimer(), lastHeader.getTimer());
if (timer < 0 || timer >= 0xffffff) {
RTMPUtils.writeMediumInt(buf, 0xffffff);
buf.putInt(timer);
} else {
RTMPUtils.writeMediumInt(buf, timer);
}
header.setTimerBase(header.getTimer() - timer);
header.setTimerDelta(timer);
break;
case HEADER_CONTINUE:
timer = (int) RTMPUtils.diffTimestamps(header.getTimer(), lastHeader.getTimer());
header.setTimerBase(header.getTimer() - timer);
header.setTimerDelta(timer);
break;
default:
break;
}
// log.trace("CHUNK, E, {}, {}", header, headerType);
}
/**
* Encode message.
*
* @param rtmp RTMP protocol state
* @param header RTMP message header
* @param message RTMP message (event)
* @return Encoded message data
*/
public IoBuffer encodeMessage(RTMP rtmp, Header header, IRTMPEvent message) {
IServiceCall call = null;
switch (header.getDataType()) {
case TYPE_CHUNK_SIZE:
return encodeChunkSize((ChunkSize) message);
case TYPE_INVOKE:
// log.trace("Invoke {}", message);
call = ((Invoke) message).getCall();
if (call != null) {
// log.debug("{}", call.toString());
Object[] args = call.getArguments();
if (args != null && args.length > 0) {
Object a0 = args[0];
if (a0 instanceof Status) {
Status status = (Status) a0;
//code: NetStream.Seek.Notify
if (StatusCodes.NS_SEEK_NOTIFY.equals(status.getCode())) {
//desc: Seeking 25000 (stream ID: 1).
int seekTime = Integer.valueOf(status.getDescription().split(" ")[1]);
// log.trace("Seek to time: {}", seekTime);
//audio and video channels
int[] channels = new int[] { 5, 6 };
//if its a seek notification, reset the "mapping" for audio (5) and video (6)
for (int channelId : channels) {
LiveTimestampMapping mapping = rtmp.getLastTimestampMapping(channelId);
if (mapping != null) {
long timestamp = mapping.getClockStartTime() + (seekTime & 0xFFFFFFFFL);
// log.trace("Setting last stream time to: {}", timestamp);
mapping.setLastStreamTime(timestamp);
} else {
log.debug("No ts mapping for channel id: {}", channelId);
}
}
}
}
}
}
return encodeInvoke((Invoke) message, rtmp);
case TYPE_NOTIFY:
// log.trace("Notify {}", message);
call = ((Notify) message).getCall();
if (call == null) {
return encodeStreamMetadata((Notify) message);
} else {
return encodeNotify((Notify) message, rtmp);
}
case TYPE_PING:
return encodePing((Ping) message);
case TYPE_BYTES_READ:
return encodeBytesRead((BytesRead) message);
case TYPE_AGGREGATE:
// log.trace("Encode aggregate message");
return encodeAggregate((Aggregate) message);
case TYPE_AUDIO_DATA:
// log.trace("Encode audio message");
return encodeAudioData((AudioData) message);
case TYPE_VIDEO_DATA:
// log.trace("Encode video message");
return encodeVideoData((VideoData) message);
case TYPE_FLEX_SHARED_OBJECT:
return encodeFlexSharedObject((ISharedObjectMessage) message, rtmp);
case TYPE_SHARED_OBJECT:
return encodeSharedObject((ISharedObjectMessage) message, rtmp);
case TYPE_SERVER_BANDWIDTH:
return encodeServerBW((ServerBW) message);
case TYPE_CLIENT_BANDWIDTH:
return encodeClientBW((ClientBW) message);
case TYPE_FLEX_MESSAGE:
return encodeFlexMessage((FlexMessage) message, rtmp);
case TYPE_FLEX_STREAM_SEND:
return encodeFlexStreamSend((FlexStreamSend) message);
default:
log.warn("Unknown object type: {}", header.getDataType());
}
return null;
}
/**
* Encode server-side bandwidth event.
*
* @param serverBW Server-side bandwidth event
* @return Encoded event data
*/
private IoBuffer encodeServerBW(ServerBW serverBW) {
final IoBuffer out = IoBuffer.allocate(4);
out.putInt(serverBW.getBandwidth());
return out;
}
/**
* Encode client-side bandwidth event.
*
* @param clientBW Client-side bandwidth event
* @return Encoded event data
*/
private IoBuffer encodeClientBW(ClientBW clientBW) {
final IoBuffer out = IoBuffer.allocate(5);
out.putInt(clientBW.getBandwidth());
out.put(clientBW.getValue2());
return out;
}
/** {@inheritDoc} */
public IoBuffer encodeChunkSize(ChunkSize chunkSize) {
final IoBuffer out = IoBuffer.allocate(4);
out.putInt(chunkSize.getSize());
return out;
}
/** {@inheritDoc} */
public IoBuffer encodeFlexSharedObject(ISharedObjectMessage so, RTMP rtmp) {
final IoBuffer out = IoBuffer.allocate(128);
out.setAutoExpand(true);
// TODO: also support sending of AMF3 encoded data
out.put((byte) 0x00);
doEncodeSharedObject(so, rtmp, out);
return out;
}
/** {@inheritDoc} */
public IoBuffer encodeSharedObject(ISharedObjectMessage so, RTMP rtmp) {
final IoBuffer out = IoBuffer.allocate(128);
out.setAutoExpand(true);
doEncodeSharedObject(so, rtmp, out);
return out;
}
/**
* Perform the actual encoding of the shared object contents.
*
* @param so shared object
* @param rtmp rtmp
* @param out output buffer
*/
public void doEncodeSharedObject(ISharedObjectMessage so, RTMP rtmp, IoBuffer out) {
final Output output = new org.red5.io.amf.Output(out);
output.putString(so.getName());
// SO version
out.putInt(so.getVersion());
// Encoding (this always seems to be 2 for persistent shared objects)
out.putInt(so.isPersistent() ? 2 : 0);
// unknown field
out.putInt(0);
int mark, len;
for (ISharedObjectEvent event : so.getEvents()) {
byte type = SharedObjectTypeMapping.toByte(event.getType());
switch (event.getType()) {
case SERVER_CONNECT:
case CLIENT_INITIAL_DATA:
case CLIENT_CLEAR_DATA:
out.put(type);
out.putInt(0);
break;
case SERVER_DELETE_ATTRIBUTE:
case CLIENT_DELETE_DATA:
case CLIENT_UPDATE_ATTRIBUTE:
out.put(type);
mark = out.position();
out.skip(4); // we will be back
output.putString(event.getKey());
len = out.position() - mark - 4;
out.putInt(mark, len);
break;
case SERVER_SET_ATTRIBUTE:
case CLIENT_UPDATE_DATA:
if (event.getKey() == null) {
// Update multiple attributes in one request
Map, ?> initialData = (Map, ?>) event.getValue();
for (Object o : initialData.keySet()) {
out.put(type);
mark = out.position();
out.skip(4); // we will be back
String key = (String) o;
output.putString(key);
serializer.serialize(output, initialData.get(key));
len = out.position() - mark - 4;
out.putInt(mark, len);
}
} else {
out.put(type);
mark = out.position();
out.skip(4); // we will be back
output.putString(event.getKey());
serializer.serialize(output, event.getValue());
len = out.position() - mark - 4;
out.putInt(mark, len);
}
break;
case CLIENT_SEND_MESSAGE:
case SERVER_SEND_MESSAGE:
// Send method name and value
out.put(type);
mark = out.position();
out.skip(4);
// Serialize name of the handler to call...
serializer.serialize(output, event.getKey());
// ...and the arguments
for (Object arg : (List>) event.getValue()) {
serializer.serialize(output, arg);
}
len = out.position() - mark - 4;
//log.debug(len);
out.putInt(mark, len);
//log.info(out.getHexDump());
break;
case CLIENT_STATUS:
out.put(type);
final String status = event.getKey();
final String message = (String) event.getValue();
out.putInt(message.length() + status.length() + 4);
output.putString(message);
output.putString(status);
break;
default:
//log.error("Unknown event " + event.getType());
// XXX: come back here, need to make this work in server or client mode
// talk to joachim about this part.
out.put(type);
mark = out.position();
//out.putInt(0);
out.skip(4); // we will be back
output.putString(event.getKey());
serializer.serialize(output, event.getValue());
len = out.position() - mark - 4;
out.putInt(mark, len);
break;
}
}
}
/** {@inheritDoc} */
public IoBuffer encodeNotify(Notify notify, RTMP rtmp) {
return encodeNotifyOrInvoke(notify, rtmp);
}
/** {@inheritDoc} */
public IoBuffer encodeInvoke(Invoke invoke, RTMP rtmp) {
return encodeNotifyOrInvoke(invoke, rtmp);
}
/**
* Encode notification event.
*
* @param invoke Notification event
* @return Encoded event data
*/
protected IoBuffer encodeNotifyOrInvoke(Notify invoke, RTMP rtmp) {
IoBuffer out = IoBuffer.allocate(1024);
out.setAutoExpand(true);
encodeNotifyOrInvoke(out, invoke, rtmp);
return out;
}
/**
* Encode notification event and fill given byte buffer.
*
* @param out Byte buffer to fill
* @param invoke Notification event
*/
protected void encodeNotifyOrInvoke(IoBuffer out, Notify invoke, RTMP rtmp) {
// TODO: tidy up here
// log.debug("Encode invoke");
Output output = new org.red5.io.amf.Output(out);
final IServiceCall call = invoke.getCall();
final boolean isPending = (call.getStatus() == Call.STATUS_PENDING);
// log.debug("Call: {} pending: {}", call, isPending);
if (!isPending) {
log.debug("Call has been executed, send result");
serializer.serialize(output, call.isSuccess() ? "_result" : "_error"); // seems right
} else {
log.debug("This is a pending call, send request");
// for request we need to use AMF3 for client mode
// if the connection is AMF3
if (rtmp.getEncoding() == Encoding.AMF3 && rtmp.getMode() == RTMP.MODE_CLIENT) {
output = new org.red5.io.amf3.Output(out);
}
final String action = (call.getServiceName() == null) ? call.getServiceMethodName() : call.getServiceName() + '.' + call.getServiceMethodName();
serializer.serialize(output, action); // seems right
}
if (invoke instanceof Invoke) {
serializer.serialize(output, Integer.valueOf(invoke.getInvokeId()));
serializer.serialize(output, invoke.getConnectionParams());
}
if (call.getServiceName() == null && "connect".equals(call.getServiceMethodName())) {
// Response to initial connect, always use AMF0
output = new org.red5.io.amf.Output(out);
} else {
if (rtmp.getEncoding() == Encoding.AMF3) {
output = new org.red5.io.amf3.Output(out);
} else {
output = new org.red5.io.amf.Output(out);
}
}
if (!isPending && (invoke instanceof Invoke)) {
IPendingServiceCall pendingCall = (IPendingServiceCall) call;
if (!call.isSuccess()) {
log.debug("Call was not successful");
StatusObject status = generateErrorResult(StatusCodes.NC_CALL_FAILED, call.getException());
pendingCall.setResult(status);
}
Object res = pendingCall.getResult();
// log.debug("Writing result: {}", res);
serializer.serialize(output, res);
} else {
// log.debug("Writing params");
final Object[] args = call.getArguments();
if (args != null) {
for (Object element : args) {
serializer.serialize(output, element);
}
}
}
if (invoke.getData() != null) {
out.setAutoExpand(true);
out.put(invoke.getData());
}
}
/** {@inheritDoc} */
public IoBuffer encodePing(Ping ping) {
int len = 6;
if (ping.getValue3() != Ping.UNDEFINED) {
len += 4;
}
if (ping.getValue4() != Ping.UNDEFINED) {
len += 4;
}
final IoBuffer out = IoBuffer.allocate(len);
out.putShort(ping.getEventType());
out.putInt(ping.getValue2());
if (ping.getValue3() != Ping.UNDEFINED) {
out.putInt(ping.getValue3());
}
if (ping.getValue4() != Ping.UNDEFINED) {
out.putInt(ping.getValue4());
}
return out;
}
/** {@inheritDoc} */
public IoBuffer encodeBytesRead(BytesRead bytesRead) {
final IoBuffer out = IoBuffer.allocate(4);
out.putInt(bytesRead.getBytesRead());
return out;
}
/** {@inheritDoc} */
public IoBuffer encodeAggregate(Aggregate aggregate) {
final IoBuffer result = aggregate.getData();
return result;
}
/** {@inheritDoc} */
public IoBuffer encodeAudioData(AudioData audioData) {
final IoBuffer result = audioData.getData();
return result;
}
/** {@inheritDoc} */
public IoBuffer encodeVideoData(VideoData videoData) {
final IoBuffer result = videoData.getData();
return result;
}
/** {@inheritDoc} */
public IoBuffer encodeUnknown(Unknown unknown) {
final IoBuffer result = unknown.getData();
return result;
}
public IoBuffer encodeStreamMetadata(Notify metaData) {
final IoBuffer result = metaData.getData();
return result;
}
/**
* Generate error object to return for given exception.
*
* @param code call
* @param error error
* @return status object
*/
protected StatusObject generateErrorResult(String code, Throwable error) {
// Construct error object to return
String message = "";
while (error != null && error.getCause() != null) {
error = error.getCause();
}
if (error != null && error.getMessage() != null) {
message = error.getMessage();
}
StatusObject status = new StatusObject(code, "error", message);
// if (error instanceof ClientDetailsException) {
// // Return exception details to client
// status.setApplication(((ClientDetailsException) error).getParameters());
// if (((ClientDetailsException) error).includeStacktrace()) {
// List stack = new ArrayList();
// for (StackTraceElement element: error.getStackTrace()) {
// stack.add(element.toString());
// }
// status.setAdditional("stacktrace", stack);
// }
// } else
if (error != null) {
status.setApplication(error.getClass().getCanonicalName());
}
return status;
}
/**
* Encodes Flex message event.
*
* @param msg Flex message event
* @param rtmp RTMP
* @return Encoded data
*/
public IoBuffer encodeFlexMessage(FlexMessage msg, RTMP rtmp) {
IoBuffer out = IoBuffer.allocate(1024);
out.setAutoExpand(true);
// Unknown byte, always 0?
out.put((byte) 0);
encodeNotifyOrInvoke(out, msg, rtmp);
return out;
}
public IoBuffer encodeFlexStreamSend(FlexStreamSend msg) {
final IoBuffer result = msg.getData();
return result;
}
private void updateTolerance() {
midTolerance = baseTolerance + (long) (baseTolerance * 0.3);
highestTolerance = baseTolerance + (long) (baseTolerance * 0.6);
}
/**
* Setter for serializer.
*
* @param serializer Serializer
*/
public void setSerializer(org.red5.io.object.Serializer serializer) {
this.serializer = serializer;
}
public void setBaseTolerance(long baseTolerance) {
this.baseTolerance = baseTolerance;
//update high and low tolerance
updateTolerance();
}
/**
* Setter for dropLiveFuture
* */
public void setDropLiveFuture(boolean dropLiveFuture) {
this.dropLiveFuture = dropLiveFuture;
}
public long getBaseTolerance() {
return baseTolerance;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy