/* * 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 nliveroid.nlr.main; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.CommandLine; import org.apache.commons.CommandLineParser; import org.apache.commons.GnuParser; import org.apache.commons.HelpFormatter; import org.apache.commons.Option; import org.apache.commons.OptionBuilder; import org.apache.commons.Options; import org.apache.commons.Parser; import android.graphics.Bitmap; import android.graphics.Rect; import android.hardware.Camera.Size; import android.net.Uri; import android.util.Log; import com.flazr.rtmp.RtmpHandshake; import com.flazr.rtmp.RtmpWriter; import com.flazr.util.Utils; public class LiveSettings implements Serializable{ private static final long serialVersionUID =-7674852638292555620L; private String host = "localhost"; private int port = 1935; private String appName = "vod"; private String streamName; private String flvFilePath; private RtmpWriter writerToSave; private String chachePathForRecord; private Map<String, Object> params; private Object[] args; private byte[] clientVersion; //最初から再生 private int start = -2; private int length = -1; private int buffer = 4096; private byte[] swfHash; private int swfSize; private int load = 1; private int loop = 1; private int threads = 10; private List<LiveSettings> clientOptionsList; //追加の設定値 private int user_fps = 2; private boolean isUseMic; private boolean isUseCam; private int v_frame_rate = 10; private int v_bit_rate = 336000; private int keyframe_interval = 36; private int sample_rate = 44100; private boolean isPortLayt = false; private float ratio; private boolean zoomSupported; private boolean isSupportedSceneMode; private boolean isEncodeStarted; private boolean isStreamStarted; private int a_bit_rate = 64000; private int a_frame_rate = 10; private List<Size> resolutionList; private int resolutionIndex = 0;//必ず昇順に並べること private boolean isBackGroundCam; private boolean isBackGroundMic; private boolean isRingCamEnable; private boolean isRingMicEnable; private int MODE = 2;//デフォ値は画像配信 private float viewAngleRatio = 0; private int sceneIndex; private boolean isSupportedFlashMode; private boolean isSupportedWhiteblMode; private boolean isSupportedColorEffects; private boolean isSupportedAntib; private Uri bmpUri; private Rect bmpRect; private Bitmap bmp; // private boolean isStereo; private float volume = 1; public LiveSettings(String[] args){ if(args != null)this.parseCommand(args); } private static final Pattern URL_PATTERN = Pattern.compile( "(rtmp.?)://" // 1) protocol + "([^/:]+)(:[0-9]+)?/" // 2) host 3) port + "([^/]+)/" // 4) app + "(.*)" // 5) play ); public void parseUrl(String url) { Matcher matcher = URL_PATTERN.matcher(url); host = matcher.group(2); String portString = matcher.group(3); portString = portString.substring(1); // skip the ':' port = portString == null ? 1935 : Integer.parseInt(portString); appName = matcher.group(4); streamName = matcher.group(5); } //========================================================================== /** * コマンドのパース * @return */ protected static Options getCliOptions() { final Options options = new Options(); options.addOption(new Option("help", "print this message")); options.addOption(OptionBuilder.withArgName("host").hasArg() .withDescription("host name").create("host")); options.addOption(OptionBuilder.withArgName("port").hasArg() .withDescription("port number").create("port")); options.addOption(OptionBuilder.withArgName("app").hasArg() .withDescription("app name").create("app")); options.addOption(OptionBuilder .withArgName("start").hasArg() .withDescription("start position (milliseconds)").create("start")); options.addOption(OptionBuilder.withArgName("length").hasArg() .withDescription("length (milliseconds)").create("length")); options.addOption(OptionBuilder.withArgName("buffer").hasArg() .withDescription("buffer duration (milliseconds)").create("buffer")); options.addOption(new Option("rtmpe", "use RTMPE (encryption)")); options.addOption(new Option("live", "publish local file to server in 'live' mode")); options.addOption(new Option("record", "publish local file to server in 'record' mode")); options.addOption(new Option("append", "publish local file to server in 'append' mode")); options.addOption(OptionBuilder.withArgName("property=value").hasArgs(2) .withValueSeparator().withDescription("add / override connection param").create("D")); options.addOption(OptionBuilder.withArgName("swf").hasArg() .withDescription("path to (decompressed) SWF for verification").create("swf")); options.addOption(OptionBuilder.withArgName("version").hasArg() .withDescription("client version to use in RTMP handshake (hex)").create("version")); options.addOption(OptionBuilder.withArgName("load").hasArg() .withDescription("no. of client connections (load testing)").create("load")); options.addOption(OptionBuilder.withArgName("loop").hasArg() .withDescription("for publish mode, loop count").create("loop")); options.addOption(OptionBuilder.withArgName("threads").hasArg() .withDescription("for load testing (load) mode, thread pool size").create("threads")); options.addOption(new Option("file", "spawn connections listed in file (load testing)")); return options; } public boolean parseCommand(final String[] args) { CommandLineParser parser = new GnuParser(); CommandLine line = null; final Options options = getCliOptions(); try { line = ((Parser) parser).parse(options, args,null,false); if(line.hasOption("help") || line.getArgs().length == 0) { HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("client [options] name [saveAs | fileToPublish]" + "\n(name can be stream name, URL or load testing script file)", options); return false; } if(line.hasOption("host")) { host = line.getOptionValue("host"); } if(line.hasOption("port")) { port = Integer.valueOf(line.getOptionValue("port")); } if(line.hasOption("app")) { appName = line.getOptionValue("app"); } if(line.hasOption("start")) { start = Integer.valueOf(line.getOptionValue("start")); } if(line.hasOption("length")) { length = Integer.valueOf(line.getOptionValue("length")); } if(line.hasOption("buffer")) { buffer = Integer.valueOf(line.getOptionValue("buffer")); } if(line.hasOption("version")) { clientVersion = Utils.fromHex(line.getOptionValue("version")); if(clientVersion.length != 4) { throw new RuntimeException("client version to use has to be 4 bytes long"); } } if(line.hasOption("D")) { // TODO integers, TODO extra args for 'play' command params = new HashMap(line.getOptionProperties("D")); } if(line.hasOption("load")) { load = Integer.valueOf(line.getOptionValue("load")); } if(line.hasOption("threads")) { threads = Integer.valueOf(line.getOptionValue("threads")); } if(line.hasOption("loop")) { loop = Integer.valueOf(line.getOptionValue("loop")); } } catch(Exception e) { Log.d("ClientOptions","Parsing failed: " + e.getMessage()); return false; } String[] actualArgs = line.getArgs(); if(line.hasOption("file")) { String fileName = actualArgs[0]; File file = new File(fileName); if(!file.exists()) { throw new RuntimeException("file does not exist: '" + fileName + "'"); } Log.d("parsing file: {}", ""+file); try { FileInputStream fis = new FileInputStream(file); BufferedReader reader = new BufferedReader(new InputStreamReader(fis)); int i = 0; String s; clientOptionsList = new ArrayList<LiveSettings>(); while ((s = reader.readLine()) != null) { i++; Log.d("parsing line", ""+i +": "+ s); String[] tempArgs = s.split("\\s"); LiveSettings tempOptions = new LiveSettings(tempArgs); clientOptionsList.add(tempOptions); } reader.close(); fis.close(); } catch(Exception e) { throw new RuntimeException(e); } } else { Matcher matcher = URL_PATTERN.matcher(actualArgs[0]); if (matcher.matches()) { parseUrl(actualArgs[0]); } else { streamName = actualArgs[0]; } } //ファイルパスをセット if(actualArgs.length > 1) { chachePathForRecord = actualArgs[1]; } return true; } //========================================================================== public String getTcUrl() { return ("rtmp://") + host + ":" + port + "/" + appName; } public int getLoad() { return load; } public void setLoad(int load) { this.load = load; } public int getLoopCount() { return loop; } public void setLoop(int loop) { this.loop = loop; } public String getFilePath() { return flvFilePath; } public void setFilePath(String path) { flvFilePath = path; } public String getAppName() { return appName; } public void setAppName(String appName) { this.appName = appName; } public void setArgs(Object ... args) { this.args = args; } public Object[] getArgs() { return args; } public void setClientVersionToUse(byte[] clientVersionToUse) { this.clientVersion = clientVersionToUse; } public byte[] getClientVersionToUse() { return clientVersion; } public void initSwfVerification(String pathToLocalSwfFile) { initSwfVerification(new File(pathToLocalSwfFile)); } public void initSwfVerification(File localSwfFile) { Log.d("initializing swf verification data for: " ,""+ localSwfFile.getAbsolutePath()); byte[] bytes = Utils.readAsByteArray(localSwfFile,localSwfFile.length()); byte[] hash = Utils.sha256(bytes, RtmpHandshake.CLIENT_CONST); swfSize = bytes.length; swfHash = hash; Log.d("swf verification initialized - size: {}, hash: {}",""+ swfSize + " "+ Utils.toHex(swfHash,0,swfHash.length,false)); } public void putParam(String key, Object value) { if(params == null) { params = new LinkedHashMap<String, Object>(); } params.put(key, value); } public void setParams(Map<String, Object> params) { this.params = params; } public Map<String, Object> getParams() { return params; } public String getStreamName() { return streamName; } public void setStreamName(String streamName) { this.streamName = streamName; } public int getStart() { return start; } public void setStart(int start) { this.start = start; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public int getBuffer() { return buffer; } public void setBuffer(int buffer) { this.buffer = buffer; } public String getHost() { return host; } public void setHost(String host) { Log.d("NLiveRoid","SETHOST " + host); this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getSaveAs() { return chachePathForRecord; } public void setSaveAs(String saveAs) { this.chachePathForRecord = saveAs; } public byte[] getSwfHash() { return swfHash; } public void setSwfHash(byte[] swfHash) { this.swfHash = swfHash; } public int getSwfSize() { return swfSize; } public void setSwfSize(int swfSize) { this.swfSize = swfSize; } public int getThreads() { return threads; } public void setThreads(int threads) { this.threads = threads; } public RtmpWriter getWriterToSave() { return writerToSave; } public void setWriterToSave(RtmpWriter writerToSave) { this.writerToSave = writerToSave; } public List<LiveSettings> getClientOptionsList() { return clientOptionsList; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("[host: '").append(host); sb.append("' port: ").append(port); sb.append(" appName: '").append(appName); sb.append("' streamName: '").append(streamName); sb.append("' saveAs: '").append(chachePathForRecord); if(clientVersion != null) { sb.append(" clientVersionToUse: '").append(Utils.toHex(clientVersion,0,clientVersion.length,false)).append('\''); } sb.append(" start: ").append(start); sb.append(" length: ").append(length); sb.append(" buffer: ").append(buffer); sb.append(" params: ").append(params); sb.append(" args: ").append(Arrays.toString(args)); if(swfHash != null) { sb.append(" swfHash: '").append(Utils.toHex(swfHash,0,swfHash.length,false)); sb.append("' swfSize: ").append(swfSize).append('\''); } sb.append(" load: ").append(load); sb.append(" loop: ").append(loop); sb.append(" threads: ").append(threads); if(getNowPortlaytResolution() != null){ sb.append(" resolution w: ").append(getNowPortlaytResolution().right); sb.append(" resolution h: ").append(getNowPortlaytResolution().bottom); } sb.append(" framerate: ").append(v_frame_rate); sb.append(" keyframe: ").append(keyframe_interval); sb.append(']'); return sb.toString(); } /** * isUseMicを取得します。 * @return isUseMic */ public boolean isUseMic() { return isUseMic; } /** * isUseMicを設定します。 * @param isUseMic isUseMic */ public void setUseMic(boolean isUseMic) { this.isUseMic = isUseMic; } /** * isUseCamを取得します。 * @return isUseCam */ public boolean isUseCam() { return isUseCam; } /** * isUseCamを設定します。 * @param isUseCam isUseCam */ public void setUseCam(boolean isUseCam) { this.isUseCam = isUseCam; } /** * user_fpsを取得します。 * @return user_fps */ public int getUser_fps() { return user_fps; } /** * user_fpsを設定します。 * @param user_fps user_fps */ public void setUser_fps(int user_fps) { this.user_fps = user_fps; } /** * bit_rateを取得します。 * @return bit_rate */ public int getV_Bit_rate() { return v_bit_rate; } /** * bit_rateを設定します。 * @param bit_rate bit_rate */ public void setBit_rate(int bit_rate) { this.v_bit_rate = bit_rate; } /** * keyframe_intervalを取得します。 * @return keyframe_interval */ public int getKeyframe_interval() { return keyframe_interval; } /** * keyframe_intervalを設定します。 * @param keyframe_interval keyframe_interval */ public void setKeyframe_interval(int keyframe_interval) { this.keyframe_interval = keyframe_interval; } public int getSampleRate() { return this.sample_rate; } public void setSampleRate(int sample_rate) { this.sample_rate = sample_rate; } /** * isPortLaytを取得します。 * @return isPortLayt */ public boolean isPortLayt() { return isPortLayt; } /** * isPortLaytを設定します。 * @param isPortLayt isPortLayt */ public void setPortLayt(boolean isPortLayt) { Log.d("NLiveRoid","setPortLayt " + isPortLayt); this.isPortLayt = isPortLayt; } public void setRatio(float ratio) { this.ratio = ratio; } public float getRatio() { return ratio; } /** * zoomSupportedを取得します。 * @return zoomSupported */ public boolean isZoomSupported() { return zoomSupported; } /** * zoomSupportedを設定します。 * @param zoomSupported zoomSupported */ public void setZoomSupported(boolean zoomSupported) { this.zoomSupported = zoomSupported; } /** * シーンモードのみ保存する * @return */ public int getSceneModeIndex() { return sceneIndex; } public void setSceneModeIndex(int parseInt) { this.sceneIndex = parseInt; } public void setSupportedSceneMode(boolean isSupportedSceneMode) { this.isSupportedSceneMode = isSupportedSceneMode; } public boolean isSupoprtedSceneMode() { return isSupportedSceneMode; } public void setSupportedFlashMode(boolean isSupportedFlashMode) { this.isSupportedFlashMode = isSupportedFlashMode; } public boolean isSupoprtedFlashMode() { return isSupportedFlashMode; } public void setSupportedWhiteblMode(boolean isSupportedWhiteblMode) { this.isSupportedWhiteblMode = isSupportedWhiteblMode; } public boolean isSupoprtedWhiteblMode() { return isSupportedWhiteblMode; } public void setSupoprtedColorEeffects(boolean isSupportedColorEffects) { this.isSupportedColorEffects = isSupportedColorEffects;; } public boolean isSupoprtedColorEeffects() { return isSupportedColorEffects; } public void setSupportedAntiB(boolean isSupportedAntib) { this.isSupportedAntib = isSupportedAntib; } public boolean isSupoprtedAntiB() { return isSupportedAntib; } public boolean isEncodeStarted() { return isEncodeStarted; } public void setEncodeStarted(boolean isencodeStarted) { this.isEncodeStarted = isencodeStarted; } public boolean isStreamStarted() { return isStreamStarted; } /** * isStreamStartedを設定します。 * @param isStreamStarted isStreamStarted */ public void setStreamStarted(boolean isStreamStarted) { this.isStreamStarted = isStreamStarted; } public int getA_Bitrate() { return this.a_bit_rate; } /** * a_bit_rateを設定します。 * @param a_bit_rate a_bit_rate */ public void setA_Bit_rate(int a_bit_rate) { this.a_bit_rate = a_bit_rate; } /** * a_frame_rateを取得します。 * @return a_frame_rate */ public int getA_Frame_rate() { return a_frame_rate; } /** * a_frame_rateを設定します。 * @param a_frame_rate a_frame_rate */ public void setA_Frame_rate(int a_frame_rate) { this.a_frame_rate = a_frame_rate; } public List<Size> getResolutionList() { return resolutionList; } public void setResolutionList(List<Size> sizes) { this.resolutionList = sizes; } /** * resolutionIndexを取得します。 * @return resolutionIndex */ public int getResolutionIndex() { return resolutionIndex; } /** * resolutionIndexを設定します。 * @param resolutionIndex resolutionIndex */ public void setNowResolution(int resolutionIndex) { Log.d("NLiveRoid","setNowResolution resolutionIndex" + resolutionIndex); this.resolutionIndex = resolutionIndex; } /** * * レイアウト用のカメラ解像度取得します。 * Camera.Sizeがインスタンス化できないのでRectで代用 * @return resolutionIndex */ public Rect getNowActualResolution() { Log.d("NLiveRoid","isPortLayt " + isPortLayt); if(resolutionList == null)return null; // if(isPortLayt){ // return new Rect(0,0,resolutionList.get(resolutionIndex).height,resolutionList.get(resolutionIndex).width); // }else{ return new Rect(0,0,resolutionList.get(resolutionIndex).width,resolutionList.get(resolutionIndex).height); // } } /** *エンコード時の解像度、実際のエンコード時はYUVのHeightも必要な為、getNowActualResolutionを使用する *なのでこのメソッドはメタデータのみで使用 * @return resolutionIndex{} */ public Rect getNowPortlaytResolution() {//左右に黒を入れることを考慮した解像度を返す if(resolutionList == null)return null; int height = resolutionList.get(resolutionIndex).width; float tmp_ratio = ((float)height)/((float)resolutionList.get(resolutionIndex).height); int width = (int)(height*tmp_ratio); Log.d("LiveSetting","getNowResolution " + width + " " + height); return new Rect(0,0,(int)(height*tmp_ratio),height);//横/縦の比率を縦に書けた値が横幅になる } /** * isBackGroundCamを取得します。 * @return isBackGroundCam */ public boolean isBackGroundCam() { return isBackGroundCam; } /** * isBackGroundCamを設定します。 * @param isBackGroundCam isBackGroundCam */ public void setBackGroundCam(boolean isBackGroundCam) { this.isBackGroundCam = isBackGroundCam; } /** * isBackGroundMicを取得します。 * @return isBackGroundMic */ public boolean isBackGroundMic() { return isBackGroundMic; } /** * isBackGroundMicを設定します。 * @param isBackGroundMic isBackGroundMic */ public void setBackGroundMic(boolean isBackGroundMic) { this.isBackGroundMic = isBackGroundMic; } /** * isRingCamEnableを取得します。 * @return isRingCamEnable */ public boolean isRingCamEnable() { return isRingCamEnable; } /** * isRingCamEnableを設定します。 * @param isRingCamEnable isRingCamEnable */ public void setRingCamEnable(boolean isRingCamEnable) { this.isRingCamEnable = isRingCamEnable; } /** * isRingMicEnableを取得します。 * @return isRingMicEnable */ public boolean isRingMicEnable() { return isRingMicEnable; } /** * isRingMicEnableを設定します。 * @param isRingMicEnable isRingMicEnable */ public void setRingMicEnable(boolean isRingMicEnable) { this.isRingMicEnable = isRingMicEnable; } //プレビュー0、スナップ1、静止画2、動画3 public int getMode() { return MODE; } /** * MODEを設定します。 * @param MODE MODE */ public void setMode(int MODE) { Log.d("LiveSetting","SETMODE --"+MODE); this.MODE = MODE; } /** * viewAngleRatioを取得します。 * @return viewAngleRatio */ public float getViewAngleRatio() { return viewAngleRatio; } /** * viewAngleRatioを設定します。 * @param viewAngleRatio viewAngleRatio */ public void setViewAngleRatio(float viewAngleRatio) { Log.d("LiveSettings","ViewAngleRatio" + viewAngleRatio); this.viewAngleRatio = viewAngleRatio; } public void culclateRatio() { Rect rect = getNowActualResolution(); if(viewAngleRatio == 1){ ratio = (float) rect.bottom/(float)rect.right; }else{ ratio = viewAngleRatio; } Log.d("LS","CULC RATIO ---- " + ratio); } /** * bmpを取得します。 * @return bmp */ public Uri getBmpPath() { return bmpUri; } public void setBmpPath(Uri path) {//ネイティブ初期化済み必須 this.bmpUri = path; } public Rect getBmpRect() { return bmpRect; } public void setBmpRect(Rect rect){ this.bmpRect = rect; } public void setBmp(Bitmap bmp){ this.bmp = bmp; } public Bitmap getBmp() { return bmp; } // public boolean isStereo() { // return isStereo; // } // public void setIsStereo(boolean isstereo) { // this.isStereo = isstereo; // } public float getVolume() { return volume; } public void setVolume(float volume) { this.volume = volume; } }