package com.flazr.rtmp.reader; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import nliveroid.nlr.main.BCPlayer; import nliveroid.nlr.main.LiveSettings; import nliveroid.nlr.main.MyToast; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.util.internal.LinkedTransferQueue; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.Rect; import android.net.Uri; import android.os.AsyncTask; import android.util.Log; import com.flazr.rtmp.RtmpHeader; import com.flazr.rtmp.RtmpMessage; import com.flazr.rtmp.client.RealTimeMic; import com.flazr.rtmp.message.MessageType; import com.flazr.rtmp.message.MetadataAmf0; import com.flazr.util.Utils; public class PictureReader implements RtmpReader { private MetadataAmf0 metadata; private boolean isStoped = false; private EncodingTask encodeTask; private LinkedTransferQueue<RtmpMessage> globalQueue; private LiveSettings liveSetting; private RealTimeMic mic; private BCPlayer player; private PictureReader reader; private byte[] encodedData; private boolean pictureStarted = false; private int[] pixelsData; public PictureReader(BCPlayer player,LiveSettings liveSettings) { this.player = player; this.liveSetting = liveSettings; globalQueue = new LinkedTransferQueue<RtmpMessage>(); } private native int initBmpNative(int w,int h, int isUseMic); private native int test(); private native int endBmp(); private native int repeatBmp(byte[] encodedData, int i); private native int encodeBmp(int[] pixcels); @Override public int init(final String path) { //画像が読み込まれていなければ、画像をセットする Log.d("PictureReader","initPictureReader " + liveSetting.getMode() +" " + liveSetting.getBmpPath() + " " + liveSetting.getBmp()); if(liveSetting == null)return -1; if(path != null){ Uri file_path = Uri.parse(path); liveSetting.setBmpPath(file_path); } liveSetting.setUser_fps(2); while(liveSetting.getBmpPath() == null){//パスが設定されていなければ設定されるまで待つ long startT = System.currentTimeMillis(); Log.d("PictureReader","setSize of BMP"); try { Thread.sleep(1000); if(System.currentTimeMillis() - startT > 60000){ return -1; } } catch (InterruptedException e) { e.printStackTrace(); } } //画像のサイズを取得する pixelsData = initBmp(); if(liveSetting.getBmpRect() == null){ return -1; } Rect size = liveSetting.getBmpRect(); Log.d("PictureReader","BMP_SIZE "+size.right + " "+size.bottom+" encodeData"+encodedData); //MetaDataをスタートする メタデータの解像度を合わせるためにこの時点でサイズがないといけない final RtmpMessage metadataAtom = new MetaDataAtom(); //メッセージタイプに渡されて、メタデータがデコードされる metadata = (MetadataAmf0) MessageType.decode(metadataAtom.getHeader(), metadataAtom.encode()); return 0; } class MetaDataAtom implements RtmpMessage { private final RtmpHeader header; private ChannelBuffer data; public MetaDataAtom() { MetadataAmf0 meta = MetadataAmf0.createMetaData(liveSetting); //タグタイプ1バイト、サイズ3バイト、タイムスタンプ3バイト、タイムスタンプ拡張1バイト、ストリームID3バイト header = meta.getHeader();//ここでヘッダーは正しく返るはず //ここでタグ内のデータがChannelBufferとして取得される→native化はここを代替してやればいい //ここでinはヘッダを読んだ後、4バイト(拡張タイムスタンプ+ストリームID)進んだ状態になって返ってくる //ここのreadでサイズ分new byte[]アロケートされている!!!!これ重いんでないか? data = meta.encode();//メタデータのデータを突っ込む byte[] ba = data.copy().toByteBuffer().array(); Log.d("PictureReader METADATA)"," "+Utils.toHex(ba,0,ba.length,true)); //ファイル呼んでないので、スキップとか無し } @Override public RtmpHeader getHeader() { return header; } @Override public ChannelBuffer encode() { return data; } @Override public void decode(final ChannelBuffer in) { data = in; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(header); sb.append(" data: ").append(data); return sb.toString(); } @Override public MessageType getMessageType() {//AbstractMessage無くす為に新たに追加 return null; } } //ここはモード判定してないけど大丈夫そう public int startEncode(){ Log.d("PictureReader","startEncode"); while(!liveSetting.isStreamStarted()){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } if(encodeTask != null && encodeTask.getStatus() != AsyncTask.Status.FINISHED){ encodeTask.cancel(true); } pictureStarted = true; encodeTask = new EncodingTask(); encodeTask.execute(); Log.d("PictureReader","started QueueTask"); return 0; } @Override public MetadataAmf0 getMetadata() { return metadata; } @Override public RtmpMessage[] getStartMessages() { return new RtmpMessage[] { metadata }; } @Override public boolean hasNext() { //とりあえずtrueを返しておく return true; } protected boolean hasPrev() { return false; } protected RtmpMessage prev() {//dataQueの1個前は取っておく? return null; } class EncodingTask extends AsyncTask<Void,Void,Integer>{ private boolean ENDFLAG = true; @Override public void onCancelled(){ super.onCancelled(); ENDFLAG = false; } @Override protected Integer doInBackground(Void... args){ Log.d("PictureReader","QTASK " + ENDFLAG + " " + liveSetting.isStreamStarted()); int returnValue = 0; //BmpのPathが最初から設定されているか、ギャラリーだったら設定されるのを待つ while(liveSetting.getBmp() == null || liveSetting.getBmpRect().width() <= 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } Rect size = liveSetting.getBmpRect(); Log.d("PictureReader","BMP_SIZE "+size.right + " "+size.bottom+" "+encodedData); //マイクを使用する場合同一スレッドにする為??ここでスタートしている //カメラでなく静止画と同期するフラグをマイクのエンコーダにセットするにので、先にinitBmpNativeを呼ぶべき if(liveSetting.isUseMic()){ if(mic == null){ mic = new RealTimeMic(player); mic.setReader(PictureReader.this); } if(!mic.isInited()&&mic.init(liveSetting)<0){ ENDFLAG = false; returnValue = -6; }else if(!mic.isRecording()){ mic.startRecording(); } Log.d("PictureReader","StartMicOnPictureReader --------------------- "); } returnValue = initBmpNative(size.right,size.bottom,liveSetting.isUseMic()? 1:0); if(returnValue<0){ //-3だったらget_linesizeでこけてる ENDFLAG = false; } System.gc(); Log.d("PictureReader","encodeBmp Before "); if(encodeBmp(pixelsData) < 0){ returnValue = -4;//普通のエンコード失敗 } System.gc(); //成功したら描画 Rect rect = liveSetting.getBmpRect(); Bitmap bmp = liveSetting.getBmp(); Log.d("PictureReader","drawSurface " + rect.right + " " + rect.bottom); player.drawCamSurface(bmp,rect.right,rect.bottom); Log.d("PictureReader","ENDFLAG liveSetting.isStreamStarted " + ENDFLAG + " " + liveSetting.isStreamStarted() + " " + encodedData); while(ENDFLAG&&liveSetting.isStreamStarted()){ Log.d("PictureReader","repeatBmp_loop "); if(encodedData != null && repeatBmp(encodedData,encodedData.length-1) < 0){ player.drawCamSurface(null,rect.right,rect.bottom); returnValue = -5;//エンコード失敗(サイズがでかすぎる場合もある?) } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } player.drawCamSurface(null,rect.right,rect.bottom); endBmp(); pictureStarted = false; Log.d("PictureReader","EncodeTask"); return returnValue; } @Override protected void onPostExecute(Integer arg){ if(arg == -1){ MyToast.customToastShow(player, "画像のサイズ取得に失敗しました"); }else if(arg == -2){ MyToast.customToastShow(player, "画像エンコーダの初期化に失敗しました"); }else if(arg == -3){ MyToast.customToastShow(player, "画像のサイズに非対応であったため、停止しましt"); }else if(arg == -4){ MyToast.customToastShow(player, "画像のエンコード処理に失敗しました"); }else if(arg == -5){ MyToast.customToastShow(player, "画像の送信に失敗しました"); }else if(arg == -6){ MyToast.customToastShow(player, "マイクの処理に失敗しました"); } } } public void setEncodedBitmap(byte[] header_,byte[] data_){ Log.d("NLiveRoid","setEncodedBmp ------------------ "); Log.d("NLiveRoid","setEncoded " +header_.length + " " + data_.length); for(int i = 0; i < data_.length && i < 10; i++){ Log.d("NLvieRoid",String.format("%02X", data_[i])); } encodedData = data_; globalQueue.offer(new BmpAtom(header_,data_)); } @Override public RtmpMessage next() { // Log.d("PictureReader","next"+globalQueue.size()); RtmpMessage tmp; while(true){ try { tmp = globalQueue.take(); Log.d("NLiveRoid","Picture next#take "+tmp); if(tmp != null){ return tmp; } Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void close() { Log.d("PictureReader","CLOSE"); new AsyncTask<Void,Void,Void>(){ @Override protected Void doInBackground(Void... params) { if(mic!=null&&mic.isRecording())mic.stopRecording(true);//マイクループを止める if(encodeTask != null && encodeTask.getStatus() != AsyncTask.Status.FINISHED){ encodeTask.cancel(true); liveSetting.setBmp(null); liveSetting.setBmpRect(null); while(pictureStarted){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } Log.d("PictureReader","Closed"); } return null; } }.execute(); } class BmpAtom implements RtmpMessage { private final RtmpHeader header; private ChannelBuffer data; //カメラプレビューのメインで呼ばれる public BmpAtom(final byte[] header_,final byte[] data_) { //とりあえずここでサイズに合わせてタグのヘッダーを生成しておく //タグタイプ1バイト、サイズ3バイト、タイムスタンプ3バイト、タイムスタンプ拡張1バイト、ストリームID3バイト Log.d("PingAtom","BmpAtom Header "+Utils.toHex(header_, 0, header_.length, true)); header = readHeader(header_); //ここでタグ内のデータがChannelBufferとして取得される→native化はここを代替してやればいい //ここでinはヘッダを読んだ後、4バイト(拡張タイムスタンプ+ストリームID)進んだ状態になって返ってくる //ここのreadでサイズ分new byte[]アロケートされている!!!!これ重いんでないか?でもしょうがないのか? data = ChannelBuffers.wrappedBuffer(ChannelBuffers.BIG_ENDIAN,data_);//タグのデータを突っ込む wrapped,copied,dynamicとあるが、wrappedでいいのかわからん // byte[] ba = data.copy().toByteBuffer().array(); // Log.d("PingAtom","PingAtomDATA"+Utils.toHex(ba,0,10,true));//これ表示できる!! } public RtmpHeader readHeader(final byte[] in) { // byte[] ba = in; byte type= in[0]; // Log.d("CamPreviewAtom","readHeader"+Utils.toHex(ba,0,ba.length,true)); final MessageType messageType = MessageType.VIDEO; // Log.d("CamPreviewAtom","TYPE:"+messageType.toString()+" VAL:"+new String(Utils.toHexChars(type))); //次の3バイト final int size = (in[1] & 0xff) << 16 | (in[2] & 0xff) << 8 | (in[3] & 0xff) << 0; //次の3バイト final int timestamp = (in[4] & 0xff) << 16 | (in[5] & 0xff) << 8 | (in[6] & 0xff) << 0; // 次の拡張タイムスタンプと、ストリームIDは非対応を意味する return new RtmpHeader(messageType, timestamp, size); } //FileChannelがChannelBufferとして確保されたもの //ヘッダーとして読み出すべき11バイトが入ってくる public RtmpHeader readHeader(final ChannelBuffer in) { byte[] ba = in.copy().array(); byte type= in.readByte(); final MessageType messageType = MessageType.valueToEnum(type); //次の3バイト final int size = in.readMedium(); //次の3バイト final int timestamp = in.readMedium(); in.skipBytes(4); // 拡張タイムスタンプと、ストリームIDは非対応を意味する return new RtmpHeader(messageType, timestamp, size); } @Override public RtmpHeader getHeader() { return header; } @Override public ChannelBuffer encode() { return data; } @Override public void decode(final ChannelBuffer in) { data = in; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(header); sb.append(" data: ").append(data); return sb.toString(); } @Override public MessageType getMessageType() {//AbstractMessage無くす為に新たに追加 return null; } } @Override public LinkedTransferQueue<RtmpMessage> getGrobalQueue() { return globalQueue; } public int[] initBmp(){ Log.d("PictureReader","preencode"); //此処が呼ばれる前のreader instanceof PictureReaderでreturn -1なので-2 パスが無ければギャラリーから取得 Bitmap bitmap = null; int[] pixels = null; try { InputStream is = player.getContentResolver().openInputStream(liveSetting.getBmpPath()); bitmap = BitmapFactory.decodeStream(is); is.close(); } catch (FileNotFoundException e) { showNoFileNotif(); e.printStackTrace(); } catch (IOException e) { showNoFileNotif(); e.printStackTrace(); } Bitmap copy = null; try{ copy = bitmap.copy(Bitmap.Config.ARGB_8888, false);//フォーマットを統一する }catch(IllegalStateException e){ e.printStackTrace(); return null; }catch(NullPointerException e){ e.printStackTrace();//ファイルが認識できませんでした return null; } int w = copy.getWidth(); int h = copy.getHeight(); Log.d("NLiveRoid","PIXEL_SIZE w:"+w + " h:" + h); //YUV変換すると、色の表現が乏しくなるので、width100に縮小する // 100:x = width:height // 100height = x*width // x=100height/width // 拡大比率 Matrix matrix = new Matrix(); float resizeWidth = w; float resizeHeight = ((float)(resizeWidth*h))/w; // 元画像 // リサイズ画像 Log.d("NLiveRoid","RE_SIZE H:"+resizeHeight); float resizeScaleWidth = (float)resizeWidth / w; float resizeScaleHeight = (float)resizeHeight / h; Log.d("NLiveRoid","PIXEL_RESIZE w:"+resizeScaleWidth + " h:" + resizeScaleHeight); matrix.postScale(resizeScaleWidth, resizeScaleHeight); Bitmap resizeBitmap = Bitmap.createBitmap(copy, 0, 0, w, h, matrix, true); try{ pixels = new int[w*h]; }catch(OutOfMemoryError e){//画像がでかすぎる場合、ここの要素数確保でエラーする e.printStackTrace(); return null; } // copy.getPixels(pixels, 0, w, 0, 0, w, h); resizeBitmap.getPixels(pixels, 0, (int)resizeWidth, 0, 0, (int)resizeWidth, (int)resizeHeight); Rect rect = new Rect(); // rect.right = w; // rect.bottom = h; rect.right = (int) Math.ceil(resizeWidth); rect.bottom = (int) Math.ceil(resizeHeight); liveSetting.setBmpRect(rect); liveSetting.setBmp(resizeBitmap); Log.d("PictureReader","PreEncoded"); return pixels; } private void showNoFileNotif(){ player.runOnUiThread(new Runnable(){ @Override public void run(){ MyToast.customToastShow(player, "画像ファイル読み込みでエラーしました"); } }); } public void stopMic() { if(mic != null)mic.stopRecording(true); } }