/* Milenia Grafter Server Copyright (c) 2007-2008 by Milan Toth. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundataion, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.milgra.server.encoder; /** RtmpDecoder class @mail milgra@milgra.com @author Milan Toth @version 20080316 Tasks of RtmpDecoder - Deserialize incoming byte stream to rtmp packetBufferA **/ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.ArrayList; import com.milgra.server.Library; import com.milgra.server.OProcess; import com.milgra.server.RtmpPacket; import com.milgra.server.SocketController; public class RtmpDecoder extends OProcess { // read - read count // next - read amount for next chunk // nextSize - header size modifiers size register // chunkSize - actual chunk size for decoder public int read; public int next; public int nextSize = -1; public int chunkSize = 128; // header decoding helpers public int headerFlag; public int headerSize; public int headerChannel; // headerSizes - header size containers // end - deserialization helper public byte [ ] headerSizes; public boolean end; // packet buffers public RtmpPacket [ ] activeBuffer; public RtmpPacket [ ] packetBufferA; public RtmpPacket [ ] packetBufferB; // packet containers public ArrayList < RtmpPacket > flvList; public ArrayList < RtmpPacket > dataList; // public RtmpPacket packet; public ByteBuffer buffer; public SocketChannel socket; public SocketController controller; /** * RtmpDecoder constructor * @param controllerX SocketController */ public RtmpDecoder ( SocketController controllerX ) { // System.out.println( System.currentTimeMillis( ) + " RtmpDecoder.construct" ); // construct buffer = ByteBuffer.allocate( Library.IOBUFFER ); headerSizes = new byte [ 4 ]; flvList = new ArrayList < RtmpPacket > ( ); dataList = new ArrayList < RtmpPacket > ( ); packetBufferA = new RtmpPacket [ 64 ]; packetBufferB = new RtmpPacket [ 64 ]; // set controller = controllerX; activeBuffer = packetBufferA; // fillup headerSizes[ 3 ] = 1; headerSizes[ 2 ] = 4; headerSizes[ 1 ] = 8; headerSizes[ 0 ] = 12; for ( int index = 0 ; index < 64 ; index ++ ) { packetBufferA[ index ] = new RtmpPacket( ); packetBufferB[ index ] = new RtmpPacket( ); packetBufferA[ index ].rtmpChannel = index; packetBufferB[ index ].rtmpChannel = index; } } /** * Gives flv packets to the given arraylist * @param packetsX container for packets */ public void giveFlvPackets ( ArrayList < RtmpPacket > packetsX ) { // System.out.println( System.currentTimeMillis( ) + " RtmpDecoder.giveFlcPackets " + packetBufferAX ); if ( flvList.size( ) > 0 ) { packetsX.addAll( flvList ); flvList.clear( ); } } /** * Gives dataa packets to the given arraylist * @param packetsX container for packets */ public void giveDataPackets ( ArrayList < RtmpPacket > packetsX ) { // System.out.println( System.currentTimeMillis( ) + " RtmpDecoder.giveDataPackets " + packetsX + " " + dataList.size() ); if ( dataList.size( ) > 0 ) { packetsX.addAll( dataList ); dataList.clear( ); } } /** * Fills up buffer for next deserialization **/ public void step ( ) { try { read = socket.read( buffer ); controller.bytesIn += read; // System.out.println( System.currentTimeMillis() + " RtmpDecoder step read: " + read ); if ( read > 0 ) getChunks( ); else if ( read == -1 ) controller.close( Library.CLOSURE ); } catch ( IOException exception ) { controller.close( exception.getMessage( ) ); } } /** * Deserializes rtmp chunks and creates packet * @throws DecodeException at incorrect header sizes */ public void getChunks ( ) { // System.out.println( System.currentTimeMillis() + " " + controller.client.id + " RtmpDecoder.getChunks " + buffer ); buffer.flip( ); do { // if a header flag is available if ( buffer.hasRemaining( ) ) { // read header flag headerFlag = buffer.get( ); headerSize = headerSizes[ ( headerFlag & 0xC0 ) >> 6 ]; headerChannel = headerFlag & 0x3F; // check for page switch // !!! buffer page flipping is still annoying, has to do something with it if ( headerChannel == 0 && nextSize == -1 ) { // switch buffer nextSize = headerSize; activeBuffer = packetBufferB; } else { // if we have a nextSize, use that packet = activeBuffer[ headerChannel ]; if ( nextSize != -1 ) headerSize = nextSize; // trying to read header if ( buffer.remaining( ) >= headerSize ) { if ( headerSize > 1 ) { // extract flv timestamp packet.flvStamp = buffer.getShort( ) << 8 | buffer.get( ) & 0xFF; if ( headerSize > 4 ) { // extract body size and type packet.bodySize = buffer.getShort( ) << 8 | buffer.get( ) & 0xFF; packet.bodyType = buffer.get( ) & 0xFF; if ( headerSize > 8 ) { // extract flv channel packet.flvChannel = buffer.getInt( ); } packet.body = new byte[ packet.bodySize ]; packet.bodyLeft = packet.bodySize; } } // if chunk size is bigger than remaining, use remaining next = chunkSize < packet.bodyLeft ? chunkSize : packet.bodyLeft; // trying to read actual chunk /* header debug byte [ ] header = new byte[ headerSize ]; buffer.position( buffer.position() - headerSize ); buffer.get( header ); System.out.println( "header : " + Encoder.getHexa( header ) ); System.out.println( "remaining : " + buffer.remaining() + " next: " + next ); //*/ if ( buffer.remaining( ) >= next ) { // reset nextSize nextSize = -1; activeBuffer = packetBufferA; buffer.get( packet.body , packet.bodySize - packet.bodyLeft , next ); // modify position and remaining packet.bodyLeft -= next; // packet is complete if ( packet.bodyLeft == 0 ) { // packet is chunk size changer if ( packet.bodyType == 0x01 ) { chunkSize = Encoder.bytesToInt( packet.body ); } else { // clone packet RtmpPacket newPacket = new RtmpPacket( packet ); // System.out.println( "packet decode: " + packet ); switch ( packet.bodyType ) { case 0x08 : case 0x09 : flvList.add( newPacket ); break; default : dataList.add( newPacket ); break; } } packet.body = new byte [ packet.bodySize ]; packet.bodyLeft = packet.bodySize; } } else { end = true; buffer.position( buffer.position( ) - headerSize ); } // no data for chunk } else { end = true; buffer.position( buffer.position( ) - 1 ); } // no data for header } // go further } else end = true; // no data left in buffer } while ( !end ); end = false; buffer.compact( ); } }