/*
* Flazr <http://flazr.com> Copyright (C) 2009 Peter Thomas.
*
* This file is part of Flazr.
*
* Flazr 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 3 of the License, or
* (at your option) any later version.
*
* Flazr 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 Flazr. If not, see <http://www.gnu.org/licenses/>.
*/
package com.flazr.rtmp;
import com.flazr.rtmp.RtmpDecoder.DecoderState;
import com.flazr.rtmp.message.ChunkSize;
import com.flazr.rtmp.message.Control;
import com.flazr.rtmp.message.MessageType;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RtmpDecoder extends ReplayingDecoder<DecoderState> {
private static final Logger logger = LoggerFactory.getLogger(RtmpDecoder.class);
public static enum DecoderState {
GET_HEADER,
GET_PAYLOAD
}
public RtmpDecoder() {
super(DecoderState.GET_HEADER);
}
private RtmpHeader header;
private int channelId;
private ChannelBuffer payload;
private int chunkSize = 128;
private final RtmpHeader[] incompleteHeaders = new RtmpHeader[RtmpHeader.MAX_CHANNEL_ID];
private final ChannelBuffer[] incompletePayloads = new ChannelBuffer[RtmpHeader.MAX_CHANNEL_ID];
private final RtmpHeader[] completedHeaders = new RtmpHeader[RtmpHeader.MAX_CHANNEL_ID];
@Override
protected Object decode(final ChannelHandlerContext ctx, final Channel channel, final ChannelBuffer in, final DecoderState state) {
switch(state) {
case GET_HEADER:
header = new RtmpHeader(in, incompleteHeaders);
channelId = header.getChannelId();
if(incompletePayloads[channelId] == null) { // new chunk stream
incompleteHeaders[channelId] = header;
incompletePayloads[channelId] = ChannelBuffers.buffer(header.getSize());
}
payload = incompletePayloads[channelId];
checkpoint(DecoderState.GET_PAYLOAD);
case GET_PAYLOAD:
final byte[] bytes = new byte[Math.min(payload.writableBytes(), chunkSize)];
in.readBytes(bytes);
payload.writeBytes(bytes);
checkpoint(DecoderState.GET_HEADER);
if(payload.writable()) { // more chunks remain
return null;
}
incompletePayloads[channelId] = null;
final RtmpHeader prevHeader = completedHeaders[channelId];
if (!header.isLarge()) {
header.setTime(prevHeader.getTime() + header.getDeltaTime());
}
final RtmpMessage message = MessageType.decode(header, payload);
if(logger.isDebugEnabled()) {
// don't print millions of PING_REQUEST
if (message.getHeader().getMessageType() != MessageType.CONTROL || ((Control) message).getType() != Control.Type.PING_REQUEST)
logger.debug("<< {}", message);
}
payload = null;
if(header.isChunkSize()) {
final ChunkSize csMessage = (ChunkSize) message;
logger.debug("decoder new chunk size: {}", csMessage);
chunkSize = csMessage.getChunkSize();
}
completedHeaders[channelId] = header;
return message;
default:
throw new RuntimeException("unexpected decoder state: " + state);
}
}
}