/*
* 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 org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelDownstreamHandler;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink;
import org.jboss.netty.channel.socket.nio.NioSocketChannel;
import android.util.Log;
import com.flazr.rtmp.message.ChunkSize;
import com.flazr.rtmp.message.Control;
import com.flazr.util.Utils;
/**
*RTMPのヘッダーやり取りをエンコードしてチャンネルに書き込む
*/
public class RtmpEncoder implements ChannelDownstreamHandler {
private int chunkSize = 128;
private RtmpHeader[] channelPrevHeaders = new RtmpHeader[RtmpHeader.MAX_CHANNEL_ID];
private NioClientSocketPipelineSink sink;
@Override
public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) {}
/**
*前のヘッダーをクリアする
*/
private void clearPrevHeaders() {
Log.d("clearing prev stream headers","");
channelPrevHeaders = new RtmpHeader[RtmpHeader.MAX_CHANNEL_ID];
}
/**
*rtmpのメッセージイベントを送信
*/
public void writeRequested(final NioSocketChannel ch, final MessageEvent e) throws Exception {
// if (e instanceof MessageEvent) {//encoder,decoder順番に来てる
Log.d("RtmpEncoder","0 handleDownStream MessageEvent ");
// try {
// sink.eventSunkMessageEvent(new DownstreamMessageEvent(ch, e.getFuture(), encode((RtmpMessage) e.getMessage()), null));
// } catch (Exception e1) {
// e1.printStackTrace();
// }
// }
}
public ChannelBuffer encode(final RtmpMessage message) {
final ChannelBuffer in = message.encode();
//メッセージヘッダー
// Log.d("RtmpEncoder","encode() "+in.capacity());
final RtmpHeader header = message.getHeader();
if(header.isChunkSize()) {
// Log.d("RtmpEncoder","header.isChunkSize()");
final ChunkSize csMessage = (ChunkSize) message;
// Log.d("RtmpEncoder ", "message: "+csMessage);
chunkSize = csMessage.getChunkSize();
} else if(header.isControl()) {
// Log.d("RtmpEncoder","header.isControl()");
final Control control = (Control) message;
if(control.getType() == Control.ControlType.STREAM_BEGIN) {
// Log.d("RtmpEncoder","STREAM_BEGIN");
clearPrevHeaders();
}
}
final int channelId = header.getChannelId();
header.setSize(in.readableBytes());
final RtmpHeader prevHeader = channelPrevHeaders[channelId];
if(prevHeader != null // first stream message is always large
&& header.getStreamId() > 0 // all control messages always large
&& header.getTime() > 0) { // if time is zero, always large
// Log.d("RtmpEncoder","prevHeader != null");
if(header.getSize() == prevHeader.getSize()) {
Log.d("RtmpEncoder","SMALL");
header.setHeaderType(RtmpHeader.HeaderType.SMALL);
} else {
// Log.d("RtmpEncoder","MEDIUM");
header.setHeaderType(RtmpHeader.HeaderType.MEDIUM);
}
final int deltaTime = header.getTime() - prevHeader.getTime();
if(deltaTime < 0) {
// Log.d("RtmpEncoder","deltaTime < 0"+ header);
header.setDeltaTime(0);
} else {
// Log.d("RtmpEncoder","deltaTime else");
header.setDeltaTime(deltaTime);
}
} else {
// Log.d("RtmpEncoder","LARGE");
// otherwise force to LARGE
header.setHeaderType(RtmpHeader.HeaderType.LARGE);
}
channelPrevHeaders[channelId] = header;
final ChannelBuffer out = ChannelBuffers.buffer(ChannelBuffers.BIG_ENDIAN,
RtmpHeader.MAX_ENCODED_SIZE + header.getSize() + header.getSize() / chunkSize);
boolean first = true;
while(in.readableBytes() > 0) {
// Log.d("RtmpEncoder","readableBytes() > 0)");
final int size = Math.min(chunkSize, in.readableBytes());
if(first) {
// Log.d("RtmpEncoder","first");
header.encode(out);
first = false;
} else {
// Log.d("RtmpEncoder","TINYHEADER");
byte[] tinyHeader = header.getTinyHeader();
out.writeBytes(tinyHeader,0,tinyHeader.length);//追加?
}
in.readBytes(out, size);
}
// if(message.getHeader().isMedia()){
// Log.d("RtmpEncoder","isMedia");
// byte[] ba = out.array();
// Log.d("RtmpEncoder","Data:"+Utils.toHex(ba, 0, ba.length, true));
// }
return out;
}
/**
* データ送信用(結局省略できたのは最初のChunkSizeの分岐だけ。。)
* @param message
* @return
*/
public ChannelBuffer dataSendEncode(final RtmpMessage message) {
final RtmpHeader header = message.getHeader();
final int channelId = header.getChannelId();
final RtmpHeader prevHeader = channelPrevHeaders[channelId];
if(prevHeader != null
&& header.getStreamId() > 0
&& header.getTime() > 0) {
if(header.getSize() == prevHeader.getSize()) {
header.setHeaderType(RtmpHeader.HeaderType.SMALL);
} else {
header.setHeaderType(RtmpHeader.HeaderType.MEDIUM);
}
final int deltaTime = header.getTime() - prevHeader.getTime();
if(deltaTime < 0) {
header.setDeltaTime(0);
} else {
header.setDeltaTime(deltaTime);
}
}else{
header.setHeaderType(RtmpHeader.HeaderType.LARGE);
}
channelPrevHeaders[channelId] = header;
final ChannelBuffer out = ChannelBuffers.buffer(ChannelBuffers.BIG_ENDIAN,
RtmpHeader.MAX_ENCODED_SIZE + header.getSize() + header.getSize() / chunkSize);
boolean first = true;
final ChannelBuffer in = message.encode();
while(in.readableBytes() > 0) {
final int size = Math.min(chunkSize, in.readableBytes());
if(first) {
header.encode(out);
first = false;
} else {
byte[] tinyHeader = header.getTinyHeader();
out.writeBytes(tinyHeader,0,tinyHeader.length);
}
in.readBytes(out, size);
}
return out;
}
public void setSink(NioClientSocketPipelineSink sink) {
this.sink = sink;
}
}