/* * 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.message; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import nliveroid.nlr.main.LiveSettings; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.buffer.DynamicChannelBuffer; import android.graphics.Rect; import android.util.Log; import com.flazr.io.f4vutil.MovieInfo; import com.flazr.io.f4vutil.TrackInfo; import com.flazr.io.f4vutil.box.STSD.VideoSD; import com.flazr.rtmp.RtmpHeader; import com.flazr.rtmp.RtmpMessage; import com.flazr.rtmp.client.Amf0Object; import com.flazr.rtmp.client.Amf0Value; import com.flazr.util.Utils; public class MetadataAmf0 implements RtmpMessage { protected String name;//常にonMetaData protected Object[] mMetaData;//メタデータのKEY=VALUEが順に入る protected final RtmpHeader header; public MetadataAmf0(String name, Object... data) {//メタデータが無かった時に生成する時に呼ばれる Log.d("MetadataAmf0","Not exists MetaData"); header = new RtmpHeader(getMessageType()); this.name = name; this.mMetaData = data; header.setSize(encode().readableBytes()); } public MetadataAmf0(RtmpHeader header, ChannelBuffer in) { this.header = header; Log.d("MetadataAmf0","Exists MetaData"+in.capacity()); decode(in); } @Override public MessageType getMessageType() { return MessageType.METADATA_AMF0; } @Override public ChannelBuffer encode() { ChannelBuffer out = new DynamicChannelBuffer(ChannelBuffers.BIG_ENDIAN,256); Amf0Value.encode(out, name); Amf0Value.encode(out, mMetaData); return out; } /** * ここでメタデータの名前と値を読み込む * Amf0Valueから、TypeTypに渡されて、型が決まり、 * valueToEnumで値が入る * BigEndianHeapChannelBufferのsuper→HeapChannelBufferのChannelBufferが呼ばれる(HeapChannelBufferのsuperはAbstract...) */ @Override public void decode(ChannelBuffer in) { name = (String) Amf0Value.decode(in); Log.d("MetadataAmf0","name:"+name);//必ずonMetaDataが入る List<Object> list = new ArrayList<Object>(); Log.d("MetadataAmf0","ChannelClass :"+in.getClass().getName()); while(in.readableBytes() > 0) {//return writerIndex - readerIndex list.add(Amf0Value.decode(in)); } mMetaData = list.toArray(); } @Override public RtmpHeader getHeader() { return header; } public Object getData(int index) { if(mMetaData == null || mMetaData.length < index + 1) { return null; } return mMetaData[index]; } private Object getValue(String key) { final Map<String, Object> map = getMap(0); if(map == null) { return null; } return map.get(key); } public void setValue(String key, Object value) { if(mMetaData == null || mMetaData.length == 0) { mMetaData = new Object[]{new LinkedHashMap<String, Object>()}; } if(mMetaData[0] == null) { mMetaData[0] = new LinkedHashMap<String, Object>(); } final Map<String, Object> map = (Map) mMetaData[0]; map.put(key, value); } public Map<String, Object> getMap(int index) { return (Map<String, Object>) getData(index); } public String getString(String key) { return (String) getValue(key); } public Boolean getBoolean(String key) { return (Boolean) getValue(key); } public Double getDouble(String key) { return (Double) getValue(key); } public double getDuration() { if(mMetaData == null || mMetaData.length == 0) { return -1; } final Map<String, Object> map = getMap(0); if(map == null) { return -1; } final Object o = map.get("duration"); if(o == null) { return -1; } return ((Double) o).longValue(); } public void setDuration(final double duration) { if(mMetaData == null || mMetaData.length == 0) { mMetaData = new Object[] {Utils.createMap(new LinkedHashMap<String, Object>(),Utils.createPair("duration", duration))}; } final Object meta = mMetaData[0]; final Map<String, Object> map = (Map) meta; if(map == null) { mMetaData[0] = Utils.createMap(new LinkedHashMap<String, Object>(),Utils.createPair("duration", duration)); return; } map.put("duration", duration); } //========================================================================== public static MetadataAmf0 onPlayStatus(double duration, double bytes) { Map<String, Object> map = Command.onStatus(Command.OnStatus.STATUS, "NetStream.Play.Complete", Utils.createPair("duration", duration), Utils.createPair("bytes", bytes)); return new MetadataAmf0("onPlayStatus", map); } public static MetadataAmf0 rtmpSampleAccess() { return new MetadataAmf0("|RtmpSampleAccess", false, false); } public static MetadataAmf0 dataStart() { return new MetadataAmf0("onStatus", Utils.createAmfObject(new Amf0Object(),Utils.createPair("code", "NetStream.Data.Start"))); } //========================================================================== /** [ (map){ duration=112.384, moovPosition=28.0, width=640.0, height=352.0, videocodecid=avc1, audiocodecid=mp4a, avcprofile=100.0, avclevel=30.0, aacaot=2.0, videoframerate=29.97002997002997, audiosamplerate=24000.0, audiochannels=2.0, trackinfo= [ (object){length=3369366.0, timescale=30000.0, language=eng, sampledescription=[(object){sampletype=avc1}]}, (object){length=2697216.0, timescale=24000.0, language=eng, sampledescription=[(object){sampletype=mp4a}]} ]}] */ //ここに何か追加するとプレビューモード時のメタデータに入る public static MetadataAmf0 createMetaData(LiveSettings liveSetting){ Log.d("NLiveRoid","createMetaData Called"); int width = 0,height = 0; if(liveSetting.getMode() == 2){ Rect nowSize = liveSetting.getBmpRect() == null? liveSetting.getNowActualResolution():liveSetting.getBmpRect(); width = nowSize.right; height = nowSize.bottom; }else if(liveSetting.getMode() == 0 || liveSetting.getMode() == 1){//プレビューかスナップ if(liveSetting.isPortLayt()){ Rect nowSize = liveSetting.getNowPortlaytResolution(); width = nowSize.right; height = nowSize.bottom; }else{ Rect nowSize = liveSetting.getNowActualResolution(); width = nowSize.right; height = nowSize.bottom; } } //Metadataが欲しい時用(一時的にnullを避ける) Map<String, Object> map = Utils.createMap(new LinkedHashMap<String, Object>(), Utils.createPair("duration", 3600), Utils.createPair("width", width), Utils.createPair("height",height), Utils.createPair("videocodecid", 2.0), Utils.createPair("audiocodecid", 2.0), Utils.createPair("audiosamplerate", 44100.0), Utils.createPair("framerate", liveSetting.getUser_fps()), Utils.createPair("encoder","NLR") ); return new MetadataAmf0("onMetaData", map); } /* * 1 JPEG (currently unused) - 2 Sorenson H.263 FLV1 3 Screen video FLV3 4 On2 VP6 FLV4 5 On2 VP6 with alpha channel FLV5 6 Screen video version 2 未対応 7 AVC 未対応 0 uncompressed 1 ADPCM 2 MP3 4 Nellymoser @ 16 kHz モノラル 5 Nellymoser、8kHz モノラル 6 Nellymoser 10 AAC 11 Speex */ public static MetadataAmf0 onMetaDataTest(MovieInfo movie) { Amf0Object track1 = Utils.createAmfObject(new Amf0Object(), Utils.createPair("length", 3369366.0), Utils.createPair("timescale", 30000.0), Utils.createPair("language", "eng"), Utils.createPair("sampledescription", new Amf0Object[]{Utils.createAmfObject(new Amf0Object(),Utils.createPair("sampletype", "avc1"))}) ); Amf0Object track2 = Utils.createAmfObject(new Amf0Object(), Utils.createPair("length", 2697216.0), Utils.createPair("timescale", 24000.0), Utils.createPair("language", "eng"), Utils.createPair("sampledescription", new Amf0Object[]{Utils.createAmfObject(new Amf0Object(),Utils.createPair("sampletype", "mp4a"))}) ); Map<String, Object> map = Utils.createMap(new LinkedHashMap<String, Object>(), Utils.createPair("duration", movie.getDuration()), Utils. createPair("moovPosition", movie.getMoovPosition()), Utils.createPair("width", 640.0), Utils.createPair("height", 352.0), Utils.createPair("videocodecid", "avc1"), Utils.createPair("audiocodecid", "mp4a"), Utils.createPair("avcprofile", 100.0), Utils.createPair("avclevel", 30.0), Utils.createPair("aacaot", 2.0), Utils.createPair("videoframerate", 29.97002997002997), Utils.createPair("audiosamplerate", 24000.0), Utils.createPair("audiochannels", 2.0), Utils.createPair("trackinfo", new Amf0Object[]{track1, track2}) ); return new MetadataAmf0("onMetaData", map); } public static MetadataAmf0 onMetaData(MovieInfo movie) { Map<String, Object> map =Utils.createMap(new LinkedHashMap<String, Object>(), Utils.createPair("duration", movie.getDuration()), Utils.createPair("moovPosition", movie.getMoovPosition()) ); TrackInfo track1 = movie.getVideoTrack(); Amf0Object t1 = null; if(track1 != null) { String sampleType = track1.getStsd().getSampleTypeString(1); t1 = Utils.createAmfObject(new Amf0Object(), Utils.createPair("length", track1.getMdhd().getDuration()), Utils.createPair("timescale", track1.getMdhd().getTimeScale()), Utils.createPair("sampledescription", new Amf0Object[]{Utils.createAmfObject(new Amf0Object(),Utils.createPair("sampletype", sampleType))}) ); VideoSD video = movie.getVideoSampleDescription(); Utils.createMap(map, Utils.createPair("width", (double) video.getWidth()), Utils.createPair("height", (double) video.getHeight()), Utils.createPair("videocodecid", sampleType) ); } TrackInfo track2 = movie.getAudioTrack(); Amf0Object t2 = null; if(track2 != null) { String sampleType = track2.getStsd().getSampleTypeString(1); t2 = Utils.createAmfObject(new Amf0Object(), Utils.createPair("length", track2.getMdhd().getDuration()), Utils.createPair("timescale", track2.getMdhd().getTimeScale()), Utils.createPair("sampledescription", new Amf0Object[]{Utils.createAmfObject(new Amf0Object(),Utils.createPair("sampletype", sampleType))}) ); Utils.createMap(map, Utils.createPair("audiocodecid", sampleType) ); } List<Amf0Object> trackList = new ArrayList<Amf0Object>(); if(t1 != null) { trackList.add(t1); } if(t2 != null) { trackList.add(t2); } Utils.createMap(map, Utils.createPair("trackinfo", trackList.toArray())); return new MetadataAmf0("onMetaData", map); } //========================================================================== public String getName() { return name; } @Override public String toString() { StringBuilder sb = new StringBuilder(); // sb.append(super.toString());//コメントにしたので問題がある場合がある // sb.append("name: ")//コメントにしたので問題がある場合がある sb.append(name)//常にonMetaData .append(":") .append(Arrays.toString(mMetaData));//メタデータのKEY=VALUEの全容 return sb.toString(); } }