/* * myLib - https://github.com/taktod/myLib * Copyright (c) 2014 ttProject. All rights reserved. * * Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3. */ package com.ttProject.flazr.rtmp; 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; import com.flazr.rtmp.RtmpHeader; import com.flazr.rtmp.RtmpMessage; import com.flazr.rtmp.RtmpDecoder.DecoderState; import com.flazr.rtmp.message.ChunkSize; import com.flazr.rtmp.message.MessageType; import com.ttProject.flazr.rtmp.message.CommandAmf0Ex; import com.ttProject.flazr.rtmp.message.CommandAmf3; import com.ttProject.flazr.rtmp.message.MetadataAmf3; import com.ttProject.util.HexUtil; /** * extend rtmpDecoder to deal with amf3 messages. * @author taktod */ public class RtmpDecoderEx extends ReplayingDecoder<DecoderState> { /** logger */ private static final Logger logger = LoggerFactory.getLogger(RtmpDecoderEx.class); /** * constructor */ public RtmpDecoderEx() { super(DecoderState.GET_HEADER); } // tmp data. 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]; /** * {@inheritDoc} */ @Override protected Object decode(ChannelHandlerContext context, Channel channel, ChannelBuffer in, DecoderState state) throws Exception { switch(state) { case GET_HEADER: header = new RtmpHeaderEx(in, incompleteHeaders); channelId = header.getChannelId(); if(incompletePayloads[channelId] == null) { 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); // check more chunk? if(payload.writable()) { return null; } // nomore incompletePayloads[channelId] = null; try { if(header.getMessageType() == MessageType.SHARED_OBJECT_AMF3) { logger.warn("unknown order, just ignore.(will shutdown process.)"); logger.info("type:{}, dump:{}", header.getMessageType(), HexUtil.toHex(bytes, true)); return null; } final RtmpHeader prevHeader = completedHeaders[channelId]; // timestamp injection for the test of overflow for time. // if(header.isLarge()) { // header.setTime(header.getTime() + 16777000); // } if(!header.isLarge()) { header.setTime(prevHeader.getTime() + header.getDeltaTime()); } final RtmpMessage message = MessageTypeDecode(header, payload); if(header.isChunkSize()) { final ChunkSize csMessage = (ChunkSize) message; chunkSize = csMessage.getChunkSize(); } completedHeaders[channelId] = header; return message; } catch (Exception e) { logger.error("decode error:", e); logger.info("------------------ type:{} -----------------", header.getMessageType()); logger.info(HexUtil.toHex(bytes, true)); return null; } default: throw new RuntimeException("unexpected decoder state:" + state); } } /** * decode rtmpmessages. * @param header * @param payload * @return */ private RtmpMessage MessageTypeDecode(RtmpHeader header, ChannelBuffer payload) { switch(header.getMessageType()) { case METADATA_AMF3: MetadataAmf3 metadata3 = new MetadataAmf3(header, payload); return metadata3.transform(); // force to use amf0 for clientHandler case COMMAND_AMF3: CommandAmf3 command3 = new CommandAmf3(header, payload); return command3.transform(); // force to use amf0 for clientHandler case COMMAND_AMF0: // need extra command amf0 here. CommandAmf0Ex command0 = new CommandAmf0Ex(header, payload); return command0.transform(); // force to use amf0 for clientHandler default: return MessageType.decode(header, payload); } } }