/* * 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 java.net.SocketAddress; import java.util.concurrent.atomic.AtomicReference; import nliveroid.nlr.main.BCPlayer; import nliveroid.nlr.main.ClientHandler; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBufferFactory; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.buffer.DynamicChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelEvent; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ChannelUpstreamHandler; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.UpstreamMessageEvent; import org.jboss.netty.channel.socket.nio.NioSocketChannel; import org.jboss.netty.handler.codec.replay.ReplayingDecoderBuffer; import android.util.Log; import com.flazr.rtmp.RtmpDecoder.DecoderState; import com.flazr.rtmp.message.ChunkSize; import com.flazr.rtmp.message.MessageType; public class RtmpDecoder<DecoderState> implements ChannelUpstreamHandler { private ClientHandler handler; enum DecoderState { GET_HEADER, GET_PAYLOAD } private final AtomicReference<ChannelBuffer> cumulation = new AtomicReference<ChannelBuffer>(); private final boolean unfold = false; private ReplayingDecoderBuffer replayable; private DecoderState state = DecoderState.GET_HEADER; private int checkpoint; public RtmpDecoder(ClientHandler aCT) { this.handler = aCT; } @Override public void handleUpstream( ChannelHandlerContext ctx, ChannelEvent e) throws Exception { Log.d("RtmpDecoder","handleUpstream"); // if (e instanceof MessageEvent) { // Log.d("RtmpDecoder","0"); // messageReceived(ctx, (MessageEvent) e); // } else if (e instanceof WriteCompletionEvent) { // Log.d("RtmpDecoder","1"); // WriteCompletionEvent evt = (WriteCompletionEvent) e; // ctx.getPipeline().sendUpstream(ctx,evt); // } else if (e instanceof ChildChannelStateEvent) { // Log.d("RtmpDecoder","2"); // ChildChannelStateEvent evt = (ChildChannelStateEvent) e; // ctx.getPipeline().sendUpstream(ctx,evt); // } else if (e instanceof ChannelStateEvent) { // ChannelStateEvent evt = (ChannelStateEvent) e; // Log.d("RtmpDecoder","3" + evt.getState()); // switch (evt.getState()) { // case OPEN: // if (Boolean.TRUE.equals(evt.getValue())) { // ctx.getPipeline().sendUpstream(ctx,evt); // } else { // channelClosed(ctx, evt); // } // break; // case BOUND: // ctx.getPipeline().sendUpstream(ctx,evt); // break; // case CONNECTED: // if (evt.getValue() != null) { // ctx.getPipeline().sendUpstream(ctx,evt); // } else { // channelDisconnected(ctx, evt); // } // break; // case INTEREST_OPS://何もやってねー //// ctx.getPipeline().sendUpstream(ctx,evt); // break; // default: // ctx.getPipeline().sendUpstream(ctx,evt); // } // } else if (e instanceof ExceptionEvent) { // Log.d("RtmpDecoder ","exceptionCaught"); // exceptionCaught(ctx, (ExceptionEvent) e); // } else { // Log.d("RtmpDecoder","4"); // ctx.getPipeline().sendUpstream(ctx,e); // } } /** * Stores the internal cumulative buffer's reader position. */ protected void checkpoint() { ChannelBuffer cumulation = this.cumulation.get(); if (cumulation != null) { checkpoint = cumulation.readerIndex(); } else { checkpoint = -1; // buffer not available (already cleaned up) } } /** * Stores the internal cumulative buffer's reader position and updates * the current decoder state. */ protected void checkpoint(DecoderState state) { checkpoint(); setState(state); } /** * Returns the current state of this decoder. * @return the current state of this decoder */ protected DecoderState getState() { return state; } /** * Sets the current state of this decoder. * @return the old state of this decoder */ protected DecoderState setState(DecoderState newState) { DecoderState oldState = state; state = newState; return oldState; } /** * Returns the actual number of readable bytes in the internal cumulative * buffer of this decoder. You usually do not need to rely on this value * to write a decoder. Use it only when you muse use it at your own risk. * This method is a shortcut to {@link #internalBuffer() internalBuffer().readableBytes()}. */ protected int actualReadableBytes() { return internalBuffer().readableBytes(); } /** * Returns the internal cumulative buffer of this decoder. You usually * do not need to access the internal buffer directly to write a decoder. * Use it only when you must use it at your own risk. */ protected ChannelBuffer internalBuffer() { ChannelBuffer buf = cumulation.get(); if (buf == null) { return ChannelBuffers.EMPTY_BUFFER; } return buf; } /** * Decodes the received data so far into a frame when the channel is * disconnected. * * @param ctx the context of this handler * @param channel the current channel * @param buffer the cumulative buffer of received packets so far. * Note that the buffer might be empty, which means you * should not make an assumption that the buffer contains * at least one byte in your decoder implementation. * @param state the current decoder state ({@code null} if unused) * * @return the decoded frame */ protected Object decodeLast( Channel channel, ChannelBuffer buffer, DecoderState state) throws Exception { return decode( channel, buffer, state); } /** * メインのヘッダ、ペイロードの送信要求が入ってくる * @param ctx * @param e * @throws Exception */ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { Log.d("RtmpDecoder","messageReceived"); // Object m = e.getMessage(); // if (!(m instanceof ChannelBuffer)) { // /* // * ここに来る時は、mのクラス名はcom.flazr.rtmp.RtmpPublisher$Conversation // * ctx.getName()はdecoder // */ // // try {//直接呼んでしまう // DefaultChannelHandlerContext next = ((DefaultChannelPipeline) ctx.getPipeline()).getActualUpstreamContext(((DefaultChannelHandlerContext)ctx).next); // if (next != null) { // Log.d("RtmpDecoder","sendUp" +next.getName()); // ((ChannelUpstreamHandler) next.getHandler()).handleUpstream(next, e); // }else{ // Log.d("END sendUp:: ",""); // } // } catch (Throwable t) { // ((DefaultChannelPipeline) ctx.getPipeline()).notifyHandlerException(e, t); // } // return; // } // // ChannelBuffer input = (ChannelBuffer) m; // if (!(input.readableBytes() > 0)) { // Log.d("RtmpDecoder","!(input.readableBytes() > 0)"); // return; // } // // Log.d("RtmpDecoder","cumulation"); // ChannelBuffer cumulation = cumulation(ctx); // cumulation.discardReadBytes(); // cumulation.writeBytes(input,input.readableBytes()); // callDecode(e.getChannel(), cumulation, e.getRemoteAddress()); } public void handleReadCumulation(Channel channel,MessageEvent e)throws Exception { ChannelBuffer input = (ChannelBuffer) e.getMessage(); ChannelBuffer cumulation = cumulation(channel); cumulation.discardReadBytes(); cumulation.writeBytes(input,input.readableBytes()); callDecode( e.getChannel(), cumulation, e.getRemoteAddress()); } public void channelDisconnected( ChannelStateEvent e) throws Exception { cleanup(e); } public void channelClosed(ChannelStateEvent e) throws Exception { cleanup(e); } public void exceptionCaught(ExceptionEvent e) throws Exception { handler.exceptionCaught(e); } public void callDecode(NioSocketChannel channel, ChannelBuffer cumulation, SocketAddress remoteAddress) throws Exception { while (cumulation.readableBytes() > 0) { Log.d("RtmpDecoder","callDecode"); int oldReaderIndex = checkpoint = cumulation.readerIndex(); Object result = null; DecoderState oldState = state; result = decode(channel, replayable, state);//ここで貰ったRTMPのメッセージをClientHandlerに流す if (result == null) { if (oldReaderIndex == cumulation.readerIndex() && oldState == state) { throw new IllegalStateException( "null cannot be returned if no data is consumed and state didn't change."); } else { // Previous data has been discarded or caused state transition. // Probably it is reading on. continue; } } if (oldReaderIndex == cumulation.readerIndex() && oldState == state) { throw new IllegalStateException( "decode() method must consume at least one byte " + "if it returned a decoded message (caused by: " + getClass() + ")"); } // A successful decode handler.messageReceived(new UpstreamMessageEvent( channel, result, remoteAddress)); if (!(cumulation.readableBytes() > 0)) { this.cumulation.set(null); replayable = ReplayingDecoderBuffer.EMPTY_BUFFER; } Log.d("RtmpDecoder","callDecode ------------- END"); } } private void unfoldAndFireMessageReceived(NioSocketChannel channel, Object result, SocketAddress remoteAddress) { if (unfold) { if (result instanceof Object[]) { Log.d("RtmpDecoder","unfoldAndFireMessageReceived 0"); for (Object r: (Object[]) result) { handler.messageReceived(new UpstreamMessageEvent( channel, r, remoteAddress)); } } else if (result instanceof Iterable<?>) { Log.d("RtmpDecoder","unfoldAndFireMessageReceived 1"); for (Object r: (Iterable<?>) result) { handler.messageReceived(new UpstreamMessageEvent( channel, r, remoteAddress)); } } else { Log.d("RtmpDecoder","unfoldAndFireMessageReceived 2"); handler.messageReceived(new UpstreamMessageEvent( channel, result, remoteAddress)); } } else { Log.d("RtmpDecoder","unfoldAndFireMessageReceived 3"); handler.messageReceived(new UpstreamMessageEvent( channel, result, remoteAddress)); } } private void cleanup(ChannelStateEvent e) throws Exception { Log.d("RtmpDecoder","cleanup"); try { ChannelBuffer cumulation = this.cumulation.getAndSet(null); if (cumulation == null) { return; } replayable.terminate(); if (cumulation.readableBytes() > 0) { // Make sure all data was read before notifying a closed channel. callDecode(e.getChannel(), cumulation, null); } // Call decodeLast() finally. Please note that decodeLast() is // called even if there's nothing more to read from the buffer to // notify a user that the connection was closed explicitly. Object partiallyDecoded = decode( e.getChannel(), replayable, state); if (partiallyDecoded != null) { unfoldAndFireMessageReceived(e.getChannel(), partiallyDecoded, null); } }finally { Log.d("RtmpDecoder","finally"); replayable = ReplayingDecoderBuffer.EMPTY_BUFFER; // ctx.getPipeline().sendUpstream(ctx,e); } } private ChannelBuffer cumulation(Channel ch) { Log.d("RtmpDecoder","cumulation"); ChannelBuffer buf = cumulation.get(); if (buf == null) { ChannelBufferFactory factory = ch.getConfig().getBufferFactory(); buf = new DynamicChannelBuffer(factory.getDefaultOrder(), 256,factory);//256メモリバッファ if (cumulation.compareAndSet(null, buf)) { replayable = new ReplayingDecoderBuffer(buf); } else { buf = cumulation.get(); } } return buf; } 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]; protected Object decode(final Channel channel, final ChannelBuffer in, final DecoderState state) { Log.d("RtmpDecoder","GET PACKET --- state: " + state); switch(state) { case GET_HEADER://ヘッダを生成→そのままPAYLOADの処理 header = new RtmpHeader(in, incompleteHeaders); channelId = header.getChannelId(); if(incompletePayloads[channelId] == null) { // new chunk stream incompleteHeaders[channelId] = header; incompletePayloads[channelId] = ChannelBuffers.buffer(ChannelBuffers.BIG_ENDIAN, 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,0,bytes.length); payload.writeBytes(bytes,0,bytes.length); checkpoint(DecoderState.GET_HEADER); if(payload.writableBytes() > 0) { // more chunks remain Log.d("RtmpDecoder","payload.writableBytes > 0"); 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); payload = null; if(header.isChunkSize()) { final ChunkSize csMessage = (ChunkSize) message; Log.d("decoder new chunk size at RtmpDecoder: ",""+ csMessage); chunkSize = csMessage.getChunkSize(); } completedHeaders[channelId] = header; return message; default: throw new RuntimeException("unexpected decoder state: " + state); } } }