/// <summary> /// Analyzes and responds if required to the given <see cref="RTMPPacket"/>. /// </summary> /// <param name="packet">The <see cref="RTMPPacket"/> to inspect amnd react to.</param> /// <returns>0 (false) for OK/Failed/error, 1 for 'Stop or Complete' (true)</returns> bool HandleInvoke(RTMPPacket packet) { bool ret = false; if (packet.m_body[0] != 0x02) // make sure it is a string method name we start with { Logger.Log("HandleInvoke: Sanity failed. no string method in invoke packet"); return false; } AMFObject obj = new AMFObject(); int nRes = obj.Decode(packet.m_body, 0, (int)packet.m_nBodySize, false); if (nRes < 0) { Logger.Log("HandleInvoke: error decoding invoke packet"); return false; } obj.Dump(); string method = obj.GetProperty(0).GetString(); double txn = obj.GetProperty(1).GetNumber(); Logger.Log(string.Format("server invoking <{0}>", method)); if (method == "_result") { string methodInvoked = m_methodCalls.Dequeue(); Logger.Log(string.Format("received result for method call <{0}>", methodInvoked)); if (methodInvoked == "connect") { if (!string.IsNullOrEmpty(Link.token)) { List<AMFObjectProperty> props = new List<AMFObjectProperty>(); obj.FindMatchingProperty("secureToken", props, int.MaxValue); if (props.Count > 0) { string decodedToken = Tea.Decrypt(props[0].GetString(), Link.token); SendSecureTokenResponse(decodedToken); } } SendServerBW(); if (!SkipCreateStream) { SendPing(3, 0, 300); SendCreateStream(); } if (!string.IsNullOrEmpty(Link.subscribepath)) SendFCSubscribe(Link.subscribepath); else if (Link.bLiveStream) SendFCSubscribe(Link.playpath); } else if (methodInvoked == "createStream") { m_stream_id = (int)obj.GetProperty(3).GetNumber(); SendPlay(); SendPing(3, (uint)m_stream_id, (uint)m_nBufferMS); } else if (methodInvoked == "play") { Playing = true; } } else if (method == "onBWDone") { if (m_nBWCheckCounter == 0) SendCheckBW(); } else if (method == "_onbwcheck") { SendCheckBWResult(txn); } else if (method == "_onbwdone") { if (m_methodCalls.Contains("_checkbw")) { string[] queue = m_methodCalls.ToArray(); m_methodCalls.Clear(); for (int i = 0; i < queue.Length; i++) if (queue[i] != "_checkbw") m_methodCalls.Enqueue(queue[i]); } } else if (method == "_error") { Logger.Log("rtmp server sent error"); } else if (method == "close") { Logger.Log("rtmp server requested close"); Close(); } else if (method == "onStatus") { string code = obj.GetProperty(3).GetObject().GetProperty("code").GetString(); string level = obj.GetProperty(3).GetObject().GetProperty("level").GetString(); Logger.Log(string.Format("onStatus: code :{0}, level: {1}", code, level)); if (code == "NetStream.Failed" || code == "NetStream.Play.Failed" || code == "NetStream.Play.StreamNotFound" || code == "NetConnection.Connect.InvalidApp") { Close(); } else if (code == "NetStream.Play.Start" || code == "NetStream.Publish.Start") { Playing = true; } else if (code == "NetStream.Play.Complete" || code == "NetStream.Play.Stop") { Close(); ret = true; } else if (code == "NetStream.Pause.Notify") { if (Pausing == 1 || Pausing == 2) { SendPause(false); Pausing = 3; } } } else if (MethodHookHandler != null) { ret = MethodHookHandler(method, obj, this); } else { } return ret; }
public bool GetExpectedPacket(string expectedMethod, out AMFObject obj) { RTMPPacket packet = null; obj = null; bool ready = false; while (!ready && IsConnected() && ReadPacket(out packet)) { if (!packet.IsReady()) continue; // keep reading until complete package has arrived if (packet.PacketType != PacketType.Invoke) Logger.Log(string.Format("Ignoring packet of type {0}", packet.PacketType)); else { if (packet.m_body[0] != 0x02) // make sure it is a string method name we start with { Logger.Log("GetExpectedPacket: Sanity failed. no string method in invoke packet"); return false; } obj = new AMFObject(); int nRes = obj.Decode(packet.m_body, 0, (int)packet.m_nBodySize, false); if (nRes < 0) { Logger.Log("GetExpectedPacket: error decoding invoke packet"); return false; } obj.Dump(); string method = obj.GetProperty(0).GetString(); double txn = obj.GetProperty(1).GetNumber(); Logger.Log(string.Format("server invoking <{0}>", method)); if (method == "_result" && m_methodCalls.Count > 0) { string methodInvoked = m_methodCalls.Dequeue(); Logger.Log(string.Format("received result for method call <{0}>", methodInvoked)); } ready = method == expectedMethod; } } return ready; }
void HandleMetadata(byte[] buffer, int offset, int size) { AMFObject obj = new AMFObject(); int nRes = obj.Decode(buffer, offset, size, false); if (nRes < 0) { //Log(LOGERROR, "%s, error decoding meta data packet", __FUNCTION__); return; } if (!Playing) obj.Dump(); string metastring = obj.GetProperty(0).GetString(); if (metastring == "onMetaData") { if (Playing) obj.Dump(); // always dump metadata for further analyzing List<AMFObjectProperty> props = new List<AMFObjectProperty>(); obj.FindMatchingProperty("duration", props, 1); if (props.Count > 0) { Duration = props[0].GetNumber(); Logger.Log(string.Format("Set duration: {0}", Duration)); } props.Clear(); obj.FindMatchingProperty("audiodatarate", props, 1); if (props.Count > 0) { int audiodatarate = (int)props[0].GetNumber(); CombinedBitrates += audiodatarate; Logger.Log(string.Format("audiodatarate: {0}", audiodatarate)); } props.Clear(); obj.FindMatchingProperty("videodatarate", props, 1); if (props.Count > 0) { int videodatarate = (int)props[0].GetNumber(); CombinedBitrates += videodatarate; Logger.Log(string.Format("videodatarate: {0}", videodatarate)); } if (CombinedTracksLength == 0) { props.Clear(); obj.FindMatchingProperty("filesize", props, int.MaxValue); if (props.Count > 0) { CombinedTracksLength = (int)props[0].GetNumber(); Logger.Log(string.Format("Set CombinedTracksLength from filesize: {0}", CombinedTracksLength)); } } if (CombinedTracksLength == 0) { props.Clear(); obj.FindMatchingProperty("datasize", props, int.MaxValue); if (props.Count > 0) { CombinedTracksLength = (int)props[0].GetNumber(); Logger.Log(string.Format("Set CombinedTracksLength from datasize: {0}", CombinedTracksLength)); } } } }
public void Reset() { m_dNumVal = 0.0; m_strVal = ""; m_objVal = null; m_type = AMFDataType.AMF_INVALID; }
public int Decode(byte[] pBuffer, int bufferOffset, int nSize, bool bDecodeName) { int nOriginalSize = nSize; if (nSize == 0 || pBuffer == null) return -1; if (pBuffer[bufferOffset] == 0x05) { m_type = AMFDataType.AMF_NULL; return 1; } if (bDecodeName && nSize < 4) // at least name (length + at least 1 byte) and 1 byte of data return -1; if (bDecodeName) { ushort nNameSize = RTMP.ReadInt16(pBuffer, bufferOffset); if (nNameSize > nSize - (short)sizeof(short)) return -1; m_strName = RTMP.ReadString(pBuffer, bufferOffset); nSize -= sizeof(short) + m_strName.Length; bufferOffset += sizeof(short) + m_strName.Length; } if (nSize == 0) return -1; nSize--; switch (pBuffer[bufferOffset]) { case (byte)AMFDataType.AMF_NUMBER: if (nSize < (int)sizeof(double)) return -1; m_dNumVal = RTMP.ReadNumber(pBuffer, bufferOffset + 1); nSize -= sizeof(double); m_type = AMFDataType.AMF_NUMBER; break; case (byte)AMFDataType.AMF_BOOLEAN: if (nSize < 1) return -1; m_dNumVal = Convert.ToDouble(RTMP.ReadBool(pBuffer, bufferOffset + 1)); nSize--; m_type = AMFDataType.AMF_BOOLEAN; break; case (byte)AMFDataType.AMF_STRING: { ushort nStringSize = RTMP.ReadInt16(pBuffer, bufferOffset + 1); if (nSize < nStringSize + (int)sizeof(short)) return -1; m_strVal = RTMP.ReadString(pBuffer, bufferOffset + 1); nSize -= sizeof(short) + nStringSize; m_type = AMFDataType.AMF_STRING; break; } case (byte)AMFDataType.AMF_OBJECT: { m_objVal = new AMFObject(); int nRes = m_objVal.Decode(pBuffer, bufferOffset + 1, nSize, true); if (nRes == -1) return -1; nSize -= nRes; m_type = AMFDataType.AMF_OBJECT; break; } case (byte)AMFDataType.AMF_MOVIECLIP: { Logger.Log("AMF_MOVIECLIP reserved!"); return -1; } case (byte)AMFDataType.AMF_NULL: case (byte)AMFDataType.AMF_UNDEFINED: case (byte)AMFDataType.AMF_UNSUPPORTED: { m_type = AMFDataType.AMF_NULL; break; } case (byte)AMFDataType.AMF_REFERENCE: { Logger.Log("AMF_REFERENCE not supported!"); return -1; } case (byte)AMFDataType.AMF_ECMA_ARRAY: { //int nMaxIndex = RTMP_LIB::CRTMP::ReadInt32(pBuffer+1); // can be zero for unlimited nSize -= 4; // next comes the rest, mixed array has a final 0x000009 mark and names, so its an object m_objVal = new AMFObject(); int nRes = m_objVal.Decode(pBuffer, bufferOffset + 5, nSize, true); if (nRes == -1) return -1; nSize -= nRes; m_type = AMFDataType.AMF_OBJECT; break; } case (byte)AMFDataType.AMF_OBJECT_END: { return -1; } case (byte)AMFDataType.AMF_STRICT_ARRAY: { int nArrayLen = RTMP.ReadInt32(pBuffer, bufferOffset + 1); nSize -= 4; m_objVal = new AMFObject(); int nRes = m_objVal.DecodeArray(pBuffer, bufferOffset + 5, nSize, nArrayLen, false); if (nRes == -1) return -1; nSize -= nRes; m_type = AMFDataType.AMF_OBJECT; break; } case (byte)AMFDataType.AMF_DATE: { if (nSize < 10) return -1; p_number = RTMP.ReadNumber(pBuffer, bufferOffset + 1); p_UTCoffset = RTMP.ReadInt16(pBuffer, bufferOffset + 9); nSize -= 10; break; } case (byte)AMFDataType.AMF_LONG_STRING: { int nStringSize = RTMP.ReadInt32(pBuffer, bufferOffset + 1); if (nSize < nStringSize + 4) return -1; m_strVal = RTMP.ReadLongString(pBuffer, bufferOffset + 1); nSize -= (4 + nStringSize); m_type = AMFDataType.AMF_STRING; break; } case (byte)AMFDataType.AMF_RECORDSET: { Logger.Log("AMF_RECORDSET reserved!"); return -1; } case (byte)AMFDataType.AMF_XML_DOC: { Logger.Log("AMF_XML_DOC not supported!"); return -1; } case (byte)AMFDataType.AMF_TYPED_OBJECT: { Logger.Log("AMF_TYPED_OBJECT not supported!"); return -1; } default: Logger.Log(string.Format("unknown datatype {0}", pBuffer[bufferOffset])); return -1; } return nOriginalSize - nSize; }
public static AMFObject ParseAMF(string amfString) { int depth = 0; AMFObject obj = new AMFObject(); string[] args = amfString.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (string arg in args) { AMFObjectProperty prop = new AMFObjectProperty(); string p; if (arg[1] == ':') { p = arg.Substring(2); switch (arg[0]) { case 'B': prop.m_type = AMFDataType.AMF_BOOLEAN; prop.p_number = int.Parse(p); break; case 'S': prop.m_type = AMFDataType.AMF_STRING; prop.m_strVal = p; break; case 'N': prop.m_type = AMFDataType.AMF_NUMBER; prop.p_number = double.Parse(p); break; case 'Z': prop.m_type = AMFDataType.AMF_NULL; break; case 'O': int i = int.Parse(p); if (i > 0) { prop.m_type = AMFDataType.AMF_OBJECT; } else { depth--; return obj; } break; default: return null; } } else if (arg[2] == ':' && arg[0] == 'N') { int secondColonIndex = arg.IndexOf(':', 3); if (secondColonIndex < 0 || depth <= 0) return null; prop.m_strName = arg.Substring(3); p = arg.Substring(secondColonIndex + 1); switch (arg[1]) { case 'B': prop.m_type = AMFDataType.AMF_BOOLEAN; prop.p_number = int.Parse(p); break; case 'S': prop.m_type = AMFDataType.AMF_STRING; prop.m_strVal = p; break; case 'N': prop.m_type = AMFDataType.AMF_NUMBER; prop.p_number = double.Parse(p); break; case 'O': prop.m_type = AMFDataType.AMF_OBJECT; break; default: return null; } } else return null; if (depth > 0) { AMFObject o2; for (int i = 0; i < depth; i++) { o2 = obj.GetProperty(obj.GetPropertyCount() - 1).GetObject(); obj = o2; } } obj.AddProperty(prop); if (prop.m_type == AMFDataType.AMF_OBJECT) depth++; } return obj; }