/* * 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.client; import java.util.concurrent.TimeUnit; import nliveroid.nlr.main.ClientHandler; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.DefaultChannelFuture; import org.jboss.netty.channel.DefaultExceptionEvent; import org.jboss.netty.channel.DownstreamMessageEvent; import org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink; import org.jboss.netty.channel.socket.nio.NioSocketChannel; import org.jboss.netty.util.HashedWheelTimer; import org.jboss.netty.util.Timeout; import org.jboss.netty.util.Timer; import org.jboss.netty.util.TimerTask; import android.os.AsyncTask; import android.util.Log; import com.flazr.rtmp.RtmpConfig; import com.flazr.rtmp.RtmpEncoder; import com.flazr.rtmp.RtmpHeader; import com.flazr.rtmp.RtmpMessage; import com.flazr.rtmp.message.Command; import com.flazr.rtmp.reader.FlvReader; import com.flazr.rtmp.reader.PictureReader; import com.flazr.rtmp.reader.PreviewReader; import com.flazr.rtmp.reader.RtmpReader; public class RtmpPublisher { private boolean isDataSending; private Timer timer = new HashedWheelTimer(RtmpConfig.TIMER_TICK_SIZE, TimeUnit.MILLISECONDS); private final int timerTickSize; private final boolean usingSharedTimer; private final boolean aggregateModeEnabled = true; private final RtmpReader reader; private int streamId; private long startTime; private long seekTime; private long timePosition; private int playLength = -1; private int bufferDuration; private int debugCount; private NioClientSocketPipelineSink sink; private RtmpEncoder encoder; private boolean waitSend; private AsyncTask<NioSocketChannel, Void, Void> writeThread; public RtmpPublisher(final RtmpReader reader, NioClientSocketPipelineSink sink,final int streamId, final int bufferDuration, boolean useSharedTimer, boolean aggregateModeEnabled,RtmpEncoder encoder) { Log.d("RtmpPublisher","Constractor ------ "); // this.aggregateModeEnabled = aggregateModeEnabled; this.usingSharedTimer = useSharedTimer; timerTickSize = RtmpConfig.TIMER_TICK_SIZE;//デフォ300 this.reader = reader; this.streamId = streamId; this.bufferDuration = bufferDuration; this.sink = sink; this.encoder = encoder; } public boolean isStarted() { return isDataSending; } public void start(final ClientHandler handler,final NioSocketChannel channel, final int seekTimeRequested, final int playLength, final RtmpMessage ... messages) { //playLengthに全体の長さをセットする Log.d("RtmpPublisher","rtmp start send message"); this.playLength = playLength; this.isDataSending = true; //開始時間 startTime = System.currentTimeMillis(); //timePositionはシークとか含めた現在位置 timePosition = seekTime; for(final RtmpMessage message : messages) { writeToStream(channel, message); } //スタートメッセージを全て書き込む for(final RtmpMessage message : reader.getStartMessages()) { writeToStream(channel, message); } if(reader != null){ if(reader instanceof PreviewReader){ if(((PreviewReader)reader).startEncode()<0){ handler.exceptionCaught(new DefaultExceptionEvent(channel,new Throwable("カメラ又はマイクの起動に失敗しました")));//エラー出して終了 } }else if(reader instanceof PictureReader){ if(((PictureReader)reader).startEncode()<0){ handler.exceptionCaught(new DefaultExceptionEvent(channel,new Throwable("画像送信ループ起動に失敗しました")));//エラー出して終了 } } } startWriteLoop(channel); } public void startWriteLoop(final NioSocketChannel channel){ //データを書き込む writeThread = new WriteThread().execute(channel); } class WriteThread extends AsyncTask<NioSocketChannel,Void,Void>{ @Override protected Void doInBackground(NioSocketChannel... params) { Log.d("RtmpPublisher","WRITETHREAD"); for(;isDataSending;){ float time = System.nanoTime(); write(params[0]); Log.d("RtmpPublisher","TIME " + (System.nanoTime()-time)); } return null; } } private void writeToStream(final NioSocketChannel channel, final RtmpMessage message) { Log.d("RtmpPublisher","WriteToStream ------ "); if(message.getHeader().getChannelId() > 2) { message.getHeader().setStreamId(streamId); message.getHeader().setTime((int) timePosition); } try { sink.eventSunkMessageEvent(new DownstreamMessageEvent(channel, new DefaultChannelFuture(channel, false), encoder.encode(message), channel.getRemoteAddress())); } catch (Exception e1) { e1.printStackTrace(); } } /** * データ送る時に此処が呼ばれまくる * @param channel */ private void write(final NioSocketChannel channel) { // Log.d("RtmpPublisher","write---- "); if(!channel.isWritable()) { Log.d("RtmpPublisher","FAILED WRITABLE ---- !!!! ");//一旦読み込み(サーバとの通信(BYTES_READ))を確認される? while(!channel.isWritable()){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } return; } final RtmpMessage message; synchronized(reader) { //=============== SYNCHRONIZE ! ================= if(reader.hasNext()) { message = reader.next(); } else { Log.d("RtmpPublisher","hasNextNULL ----"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } message = null; } } //==================================================================== if(!isDataSending||message == null){ Log.d("RtmpPublisher","!DataSending||message_null ----"); return; } final RtmpHeader header = message.getHeader(); header.setStreamId(streamId); /** * if (message == null || playLength >= 0 && timePosition > (seekTime + playLength)) {//playLength再生長さよりtimePosition現在位置が超えてたら終了 stop(channel); return; } //経過時間 final long elapsedTime = System.currentTimeMillis() - startTime; //経過時間+シーク時間 final long elapsedTimeAndSeek = elapsedTime + seekTime; //再生のクライアント側の余裕? final double clientBuffer = timePosition - elapsedTimeAndSeek; if(aggregateModeEnabled && clientBuffer > timerTickSize) { // TODO cleanup reader.setAggregateDuration((int) clientBuffer); } else { reader.setAggregateDuration(0); } final double compensationFactor = clientBuffer / (bufferDuration + timerTickSize); final long delay = (long) ((header.getTime() - timePosition) * compensationFactor); timePosition = header.getTime(); */ // Log.d("RtmpPublisher","OFFER --- " + message.getHeader().getSize()); // boolean offered = channel.writeBuffer.offer(new DownstreamMessageEvent(channel, new DefaultChannelFuture(channel, false), encoder.dataSendEncode(message)));//書き込み予約をセットする // if(!offered){ // Log.d("RtmpPublisher","FAILED OFFER---------"); // return; // } // channel.worker.writeFromUserCode(channel);//ここが実際にNioWorkerのwrite0からSendBufferPoolに渡されて送信される Log.d("NLiveRoid","SEND_DATA " +message.getMessageType()); Log.d("NLiveRoid","SEND_DATA_ " +message.toString()); Log.d("NLiveRoid","SEND_DATA_ " +message.getHeader().getTime()); Log.d("NLiveRoid","SEND_DATA_ " +message.getHeader().getHeaderType()); //futureをオブジェクト化しているので意味がない //送信ビーンを作成して送信 DefaultChannelFuture future = new DefaultChannelFuture(channel, false); boolean offered = channel.writeBuffer.offer(new DownstreamMessageEvent(channel, future, encoder.encode(message)));//書き込み予約をセットする assert offered; channel.worker.writeFromUserCode(channel);//ここが実際にNioWorkerのwrite0からSendBufferPoolに渡されて送信される future.addListener(new ChannelFutureListener() { @Override public void operationComplete(final ChannelFuture cf) { Log.d("RtmpPublisher","futureComp " +cf.isSuccess()); //メインルーティンの次呼び // fireNext(channel, 0); } }); // if(!future.isSuccess()){ // Log.d("RtmpPublisher","FUTURE FAILED --------- !!!!"); // } } public void fireNext(final NioSocketChannel channel, final long delay) { // Log.d("RtmpPublisher", "fireNext "+delay); if(delay > timerTickSize) { timer.newTimeout(new TimerTask() {//送信時間に余裕がでている時だと思う→ほぼ無い @Override public void run(Timeout timeout) { Log.d("Timeout delay: ", ""+delay); if(!isDataSending)return; write(channel); } }, delay, TimeUnit.MILLISECONDS); } else { //エラーが毎回発生する try{ write(channel); }catch(Exception e){ e.printStackTrace(); } } } public void close(NioSocketChannel channel) { Log.d("RtmpPublisher","RtmpPublisher CLOSE"); isDataSending = false; if(!usingSharedTimer) { timer.stop(); } RtmpMessage closeMessage = Command.unpublish(streamId); writeToStream(channel, closeMessage); reader.close(); } public void waitDataSend() { this.waitSend = true; Log.d("RtmpPublisher","waitDataSend"); if(writeThread != null && writeThread.getStatus() == AsyncTask.Status.RUNNING){ try{ writeThread.cancel(true); }catch(Exception e){ Log.d("RtmpPublisher","Interrupted"); } } } public void restartDataSend(final NioSocketChannel channel) { Log.d("RtmpPublisher","RESTART"); this.waitSend = false; if(writeThread != null && writeThread.getStatus() == AsyncTask.Status.RUNNING){ writeThread.cancel(true); } writeThread = new WriteThread().execute(channel); } public boolean isWaitDataSend() { return waitSend; } public void setAggregateDuration(int duration){ if(reader instanceof FlvReader){ ((FlvReader)reader).setAggregateDuration(duration); } } public long getDataDuration() { return ((FlvReader)reader).getDuration(); } }