bool SendSecureTokenResponse(string resp) { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x03; /* control channel (invoke) */ packet.HeaderType = HeaderType.Medium; packet.PacketType = PacketType.Invoke; Logger.Log(string.Format("Sending SecureTokenResponse: {0}", resp)); List<byte> enc = new List<byte>(); EncodeString(enc, "secureTokenResponse"); EncodeNumber(enc, 0.0); enc.Add(0x05); // NULL EncodeString(enc, resp); packet.m_nBodySize = (uint)enc.Count; packet.m_body = enc.ToArray(); return SendPacket(packet, false); }
void HandleMetadata(RTMPPacket packet) { HandleMetadata(packet.m_body, 0, (int)packet.m_nBodySize); }
bool SendPlay() { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x08; // we make 8 our stream channel packet.HeaderType = HeaderType.Large; packet.PacketType = PacketType.Invoke; packet.m_nInfoField2 = m_stream_id; packet.AllocPacket(256); // should be enough List<byte> enc = new List<byte>(); EncodeString(enc, "play"); EncodeNumber(enc, ++m_numInvokes); enc.Add(0x05); // NULL EncodeString(enc, Link.playpath); /* Optional parameters start and len. * * start: -2, -1, 0, positive number * -2: looks for a live stream, then a recorded stream, if not found any open a live stream * -1: plays a live stream * >=0: plays a recorded streams from 'start' milliseconds */ if (Link.bLiveStream) { EncodeNumber(enc, -1000.0d); } else { if (Link.seekTime > 0.0) EncodeNumber(enc, Link.seekTime); else EncodeNumber(enc, 0.0d); } /* len: -1, 0, positive number * -1: plays live or recorded stream to the end (default) * 0: plays a frame 'start' ms away from the beginning * >0: plays a live or recoded stream for 'len' milliseconds */ packet.m_body = enc.ToArray(); packet.m_nBodySize = (uint)enc.Count; Logger.Log(string.Format("Sending play: '{0}' from time: '{1}'", Link.playpath, m_channelTimestamp[m_mediaChannel])); return SendPacket(packet); }
void HandleChangeChunkSize(RTMPPacket packet) { if (packet.m_nBodySize >= 4) { InChunkSize = ReadInt32(packet.m_body, 0); Logger.Log(string.Format("received: chunk size change to {0}", InChunkSize)); } }
void HandleFlvTags(RTMPPacket packet) { // go through FLV packets and handle metadata packets int pos = 0; uint nTimeStamp = packet.m_nTimeStamp; while (pos + 11 < packet.m_nBodySize) { int dataSize = ReadInt24(packet.m_body, pos + 1); // size without header (11) and prevTagSize (4) if (pos + 11 + dataSize + 4 > packet.m_nBodySize) { Logger.Log("Stream corrupt?!"); break; } if (packet.m_body[pos] == 0x12) { HandleMetadata(packet.m_body, pos + 11, dataSize); } else if (packet.m_body[pos] == 8 || packet.m_body[pos] == 9) { nTimeStamp = (uint)ReadInt24(packet.m_body, pos + 4); nTimeStamp |= (uint)(packet.m_body[pos + 7] << 24); } pos += (11 + dataSize + 4); } if (Pausing == 0) m_mediaStamp = nTimeStamp; }
public bool SendCreateStream() { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x03; // control channel (invoke) packet.HeaderType = HeaderType.Medium; packet.PacketType = PacketType.Invoke; Logger.Log("Sending createStream"); packet.AllocPacket(256); // should be enough List<byte> enc = new List<byte>(); EncodeString(enc, "createStream"); EncodeNumber(enc, ++m_numInvokes); enc.Add(0x05); // NULL packet.m_nBodySize = (uint)enc.Count; packet.m_body = enc.ToArray(); return SendPacket(packet); }
public bool SendRequestData(string id, string request) { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x03; // control channel (invoke) packet.HeaderType = HeaderType.Large; packet.PacketType = PacketType.Invoke_AMF3; List<byte> enc = new List<byte>(); enc.Add(0x00); EncodeString(enc, "requestData"); EncodeNumber(enc, 0); enc.Add(0x05); // NULL EncodeString(enc, id); EncodeString(enc, request); packet.m_body = enc.ToArray(); packet.m_nBodySize = (uint)enc.Count; Logger.Log(string.Format("Sending requestData: ({0},{1})", id, request)); return SendPacket(packet); }
bool SendCheckBWResult(double txn) { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x03; // control channel (invoke) packet.HeaderType = HeaderType.Medium; packet.PacketType = PacketType.Invoke; packet.m_nTimeStamp = (uint)(0x16 * m_nBWCheckCounter); // temp inc value. till we figure it out. packet.AllocPacket(256); // should be enough List<byte> enc = new List<byte>(); EncodeString(enc, "_result"); EncodeNumber(enc, txn); enc.Add(0x05); // NULL EncodeNumber(enc, (double)m_nBWCheckCounter++); packet.m_nBodySize = (uint)enc.Count; packet.m_body = enc.ToArray(); return SendPacket(packet, false); }
bool SendConnect() { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x03; // control channel (invoke) packet.HeaderType = HeaderType.Large; packet.PacketType = PacketType.Invoke; packet.AllocPacket(4096); Logger.Log("Sending connect"); List<byte> enc = new List<byte>(); EncodeString(enc, "connect"); EncodeNumber(enc, ++m_numInvokes); enc.Add(0x03); //Object Datatype EncodeString(enc, "app", Link.app); Logger.Log(string.Format("app : {0}", Link.app)); if (String.IsNullOrEmpty(Link.flashVer)) EncodeString(enc, "flashVer", "WIN 10,0,32,18"); else EncodeString(enc, "flashVer", Link.flashVer); if (!string.IsNullOrEmpty(Link.swfUrl)) EncodeString(enc, "swfUrl", Link.swfUrl); EncodeString(enc, "tcUrl", Link.tcUrl); Logger.Log(string.Format("tcUrl : {0}", Link.tcUrl)); EncodeBoolean(enc, "fpad", false); EncodeNumber(enc, "capabilities", 15.0); EncodeNumber(enc, "audioCodecs", 3191.0); EncodeNumber(enc, "videoCodecs", 252.0); EncodeNumber(enc, "videoFunction", 1.0); if (!string.IsNullOrEmpty(Link.pageUrl)) EncodeString(enc, "pageUrl", Link.pageUrl); //EncodeNumber(enc, "objectEncoding", 0.0); enc.Add(0); enc.Add(0); enc.Add(0x09); // end of object - 0x00 0x00 0x09 // add auth string if (!string.IsNullOrEmpty(Link.auth)) { EncodeBoolean(enc, true); EncodeString(enc, Link.auth); } // add aditional arbitrary AMF connection properties if (Link.extras != null) { foreach (AMFObjectProperty aProp in Link.extras.m_properties) aProp.Encode(enc); } Array.Copy(enc.ToArray(), packet.m_body, enc.Count); packet.m_nBodySize = (uint)enc.Count; return SendPacket(packet); }
bool SendBytesReceived() { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x02; // control channel (invoke) packet.HeaderType = HeaderType.Medium; packet.PacketType = PacketType.BytesRead; packet.AllocPacket(4); packet.m_nBodySize = 4; List<byte> enc = new List<byte>(); EncodeInt32(enc, bytesReadTotal); packet.m_nBodySize = (uint)enc.Count; packet.m_body = enc.ToArray(); lastSentBytesRead = bytesReadTotal; Logger.Log(string.Format("Send bytes report. ({0} bytes)", bytesReadTotal)); return SendPacket(packet, false); }
bool SendCheckBW() { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x03; // control channel (invoke) packet.HeaderType = HeaderType.Large; packet.PacketType = PacketType.Invoke; //packet.m_nInfoField1 = System.Environment.TickCount; Logger.Log("Sending _checkbw"); List<byte> enc = new List<byte>(); EncodeString(enc, "_checkbw"); EncodeNumber(enc, ++m_numInvokes); enc.Add(0x05); // NULL packet.m_nBodySize = (uint)enc.Count; packet.m_body = enc.ToArray(); // triggers _onbwcheck and eventually results in _onbwdone return SendPacket(packet, false); }
bool ReadPacket(out RTMPPacket packet) { // Chunk Basic Header (1, 2 or 3 bytes) // the two most significant bits hold the chunk type // value in the 6 least significant bits gives the chunk stream id (0,1,2 are reserved): 0 -> 3 byte header | 1 -> 2 byte header | 2 -> low level protocol message | 3-63 -> stream id byte[] singleByteToReadBuffer = new byte[1]; if (ReadN(singleByteToReadBuffer, 0, 1) != 1) { Logger.Log("failed to read RTMP packet header"); packet = null; return false; } byte type = singleByteToReadBuffer[0]; byte headerType = (byte)((type & 0xc0) >> 6); int channel = (byte)(type & 0x3f); if (channel == 0) { if (ReadN(singleByteToReadBuffer, 0, 1) != 1) { Logger.Log("failed to read RTMP packet header 2nd byte"); packet = null; return false; } channel = singleByteToReadBuffer[0]; channel += 64; //header++; } else if (channel == 1) { int tmp; byte[] hbuf = new byte[2]; if (ReadN(hbuf, 0, 2) != 2) { Logger.Log("failed to read RTMP packet header 3rd and 4th byte"); packet = null; return false; } tmp = ((hbuf[2]) << 8) + hbuf[1]; channel = tmp + 64; Logger.Log(string.Format("channel: {0}", channel)); //header += 2; } uint nSize = packetSize[headerType]; //Logger.Log(string.Format("reading RTMP packet chunk on channel {0}, headersz {1}", channel, nSize)); if (nSize < RTMP_LARGE_HEADER_SIZE) packet = m_vecChannelsIn[channel]; // using values from the last message of this channel else packet = new RTMPPacket() { HeaderType = (HeaderType)headerType, m_nChannel = channel, m_hasAbsTimestamp = true }; // new packet nSize--; byte[] header = new byte[RTMP_LARGE_HEADER_SIZE]; if (nSize > 0 && ReadN(header, 0, (int)nSize) != nSize) { Logger.Log(string.Format("failed to read RTMP packet header. type: {0}", type)); return false; } if (nSize >= 3) { packet.m_nTimeStamp = (uint)ReadInt24(header, 0); if (nSize >= 6) { packet.m_nBodySize = (uint)ReadInt24(header, 3); //Logger.Log(string.Format("new packet body to read {0}", packet.m_nBodySize)); packet.m_nBytesRead = 0; packet.Free(); // new packet body if (nSize > 6) { if (Enum.IsDefined(typeof(PacketType), header[6])) packet.PacketType = (PacketType)header[6]; else Logger.Log(string.Format("Unknown packet type received: {0}", header[6])); if (nSize == 11) packet.m_nInfoField2 = ReadInt32LE(header, 7); } } if (packet.m_nTimeStamp == 0xffffff) { byte[] extendedTimestampDate = new byte[4]; if (ReadN(extendedTimestampDate, 0, 4) != 4) { Logger.Log("failed to read extended timestamp"); return false; } packet.m_nTimeStamp = (uint)ReadInt32(extendedTimestampDate, 0); } } if (packet.m_nBodySize >= 0 && packet.m_body == null && !packet.AllocPacket((int)packet.m_nBodySize)) { //CLog::Log(LOGDEBUG,"%s, failed to allocate packet", __FUNCTION__); return false; } uint nToRead = packet.m_nBodySize - packet.m_nBytesRead; uint nChunk = (uint)InChunkSize; if (nToRead < nChunk) nChunk = nToRead; int read = ReadN(packet.m_body, (int)packet.m_nBytesRead, (int)nChunk); if (read != nChunk) { Logger.Log(string.Format("failed to read RTMP packet body. total:{0}/{1} chunk:{2}/{3}", packet.m_nBytesRead, packet.m_nBodySize, read, nChunk)); packet.m_body = null; // we dont want it deleted since its pointed to from the stored packets (m_vecChannelsIn) return false; } packet.m_nBytesRead += nChunk; // keep the packet as ref for other packets on this channel m_vecChannelsIn[packet.m_nChannel] = packet.ShallowCopy(); if (packet.IsReady()) { //Logger.Log(string.Format("packet with {0} bytes read", packet.m_nBytesRead)); // make packet's timestamp absolute if (!packet.m_hasAbsTimestamp) packet.m_nTimeStamp += m_channelTimestamp[packet.m_nChannel]; // timestamps seem to be always relative!! m_channelTimestamp[packet.m_nChannel] = packet.m_nTimeStamp; // reset the data from the stored packet. we keep the header since we may use it later if a new packet for this channel // arrives and requests to re-use some info (small packet header) m_vecChannelsIn[packet.m_nChannel].m_body = null; m_vecChannelsIn[packet.m_nChannel].m_nBytesRead = 0; m_vecChannelsIn[packet.m_nChannel].m_hasAbsTimestamp = false; // can only be false if we reuse header } return true; }
void HandleServerBW(RTMPPacket packet) { m_nServerBW = ReadInt32(packet.m_body, 0); Logger.Log(string.Format("HandleServerBW: server BW = {0}", m_nServerBW)); }
void HandlePing(RTMPPacket packet) { short nType = -1; if (packet.m_body != null && packet.m_nBodySize >= 2) nType = (short)ReadInt16(packet.m_body, 0); Logger.Log(string.Format("received: ping, type: {0}", nType)); if (packet.m_nBodySize >= 6) { uint nTime = (uint)ReadInt32(packet.m_body, 2); switch (nType) { case 0: Logger.Log(string.Format("Stream Begin {0}", nTime)); break; case 1: Logger.Log(string.Format("Stream EOF {0}", nTime)); if (Pausing == 1) Pausing = 2; break; case 2: Logger.Log(string.Format("Stream Dry {0}", nTime)); break; case 4: Logger.Log(string.Format("Stream IsRecorded {0}", nTime)); break; case 6: // server ping. reply with pong. Logger.Log(string.Format("Ping {0}", nTime)); SendPing(0x07, nTime, 0); break; case 31: Logger.Log(string.Format("Stream BufferEmpty {0}", nTime)); if (!Link.bLiveStream) { if (Pausing == 0) { SendPause(true); Pausing = 1; } else if (Pausing == 2) { SendPause(false); Pausing = 3; } } break; case 32: Logger.Log(string.Format("Stream BufferReady {0}", nTime)); break; default: Logger.Log(string.Format("Stream xx {0}", nTime)); break; } } if (nType == 0x1A) { if (packet.m_nBodySize > 2 && packet.m_body[2] > 0x01) { Logger.Log(string.Format("SWFVerification Type {0} request not supported! Patches welcome...", packet.m_body[2])); } else if (Link.SWFHash != null) // respond with HMAC SHA256 of decompressed SWF, key is the 30 byte player key, also the last 30 bytes of the server handshake are applied { SendPing(0x1B, 0, 0); } else { Logger.Log("Ignoring SWFVerification request, swfhash and swfsize parameters not set!"); } } }
bool SendServerBW() { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x02; // control channel (invoke) packet.HeaderType = HeaderType.Large; packet.PacketType = PacketType.ServerBW; packet.AllocPacket(4); packet.m_nBodySize = 4; List<byte> bytesToSend = new List<byte>(); EncodeInt32(bytesToSend, m_nServerBW); // was hard coded : 0x001312d0 packet.m_body = bytesToSend.ToArray(); return SendPacket(packet, false); }
bool SendDeleteStream() { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x03; // control channel (invoke) packet.HeaderType = HeaderType.Medium; packet.PacketType = PacketType.Invoke; Logger.Log("Sending deleteStream"); List<byte> enc = new List<byte>(); EncodeString(enc, "deleteStream"); EncodeNumber(enc, ++m_numInvokes); enc.Add(0x05); // NULL EncodeNumber(enc, m_stream_id); packet.m_nBodySize = (uint)enc.Count; packet.m_body = enc.ToArray(); /* no response expected */ return SendPacket(packet, false); }
public int GetNextMediaPacket(out RTMPPacket packet) { int bHasMediaPacket = 0; packet = null; while (bHasMediaPacket == 0 && IsConnected() && ReadPacket(out packet)) { if (!packet.IsReady()) continue; // keep reading until complete package has arrived bHasMediaPacket = ClientPacket(packet); if (bHasMediaPacket > 0 && Pausing == 3) Pausing = 0; packet.m_nBytesRead = 0; } if (bHasMediaPacket > 0) Playing = true; return bHasMediaPacket; }
bool SendFCSubscribe(string subscribepath) { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x03; // control channel (invoke) packet.HeaderType = HeaderType.Medium; packet.PacketType = PacketType.Invoke; Logger.Log(string.Format("Sending FCSubscribe: {0}", subscribepath)); List<byte> enc = new List<byte>(); EncodeString(enc, "FCSubscribe"); EncodeNumber(enc, ++m_numInvokes); enc.Add(0x05); // NULL EncodeString(enc, subscribepath); packet.m_nBodySize = (uint)enc.Count; packet.m_body = enc.ToArray(); return SendPacket(packet); }
public bool SendFlex(string name, double number) { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x03; // control channel (invoke) packet.HeaderType = HeaderType.Large; packet.PacketType = PacketType.Invoke_AMF3; List<byte> enc = new List<byte>(); enc.Add(0x00); EncodeString(enc, name); EncodeNumber(enc, 0); enc.Add(0x05); // NULL EncodeNumber(enc, number); packet.m_body = enc.ToArray(); packet.m_nBodySize = (uint)enc.Count; Logger.Log(string.Format("Sending flex: ({0},{1})", name, number.ToString())); return SendPacket(packet); }
bool SendPacket(RTMPPacket packet, bool queue = true) { uint last = 0; uint t = 0; RTMPPacket prevPacket = m_vecChannelsOut[packet.m_nChannel]; if (packet.HeaderType != HeaderType.Large && prevPacket != null) { // compress a bit by using the prev packet's attributes if (prevPacket.m_nBodySize == packet.m_nBodySize && prevPacket.PacketType == packet.PacketType && packet.HeaderType == HeaderType.Medium) packet.HeaderType = HeaderType.Small; if (prevPacket.m_nTimeStamp == packet.m_nTimeStamp && packet.HeaderType == HeaderType.Small) packet.HeaderType = HeaderType.Minimum; last = prevPacket.m_nTimeStamp; } uint nSize = packetSize[(byte)packet.HeaderType]; t = packet.m_nTimeStamp - last; List<byte> header = new List<byte>();//byte[RTMP_LARGE_HEADER_SIZE]; byte c = (byte)(((byte)packet.HeaderType << 6) | packet.m_nChannel); header.Add(c); if (nSize > 1) EncodeInt24(header, (int)t); if (nSize > 4) { EncodeInt24(header, (int)packet.m_nBodySize); header.Add((byte)packet.PacketType); } if (nSize > 8) EncodeInt32LE(header, packet.m_nInfoField2); uint hSize = nSize; byte[] headerBuffer = header.ToArray(); nSize = packet.m_nBodySize; byte[] buffer = packet.m_body; uint bufferOffset = 0; uint nChunkSize = (uint)outChunkSize; while (nSize + hSize > 0) { if (nSize < nChunkSize) nChunkSize = nSize; if (hSize > 0) { byte[] combinedBuffer = new byte[headerBuffer.Length + nChunkSize]; Array.Copy(headerBuffer, combinedBuffer, headerBuffer.Length); Array.Copy(buffer, (int)bufferOffset, combinedBuffer, headerBuffer.Length, (int)nChunkSize); WriteN(combinedBuffer, 0, combinedBuffer.Length); hSize = 0; } else { WriteN(buffer, (int)bufferOffset, (int)nChunkSize); } nSize -= nChunkSize; bufferOffset += nChunkSize; if (nSize > 0) { byte sep = (byte)(0xc0 | c); hSize = 1; headerBuffer = new byte[1] { sep }; } } if (packet.PacketType == PacketType.Invoke && queue) // we invoked a remote method, keep it in call queue till result arrives m_methodCalls.Enqueue(ReadString(packet.m_body, 1)); m_vecChannelsOut[packet.m_nChannel] = packet; //m_vecChannelsOut[packet.m_nChannel].m_body = null; return true; }
/// <summary> /// Reacts corresponding to the packet. /// </summary> /// <param name="packet"></param> /// <returns>0 - no media packet, 1 - media packet, 2 - play complete</returns> int ClientPacket(RTMPPacket packet) { int bHasMediaPacket = 0; switch (packet.PacketType) { case PacketType.ChunkSize: HandleChangeChunkSize(packet); break; case PacketType.BytesRead: //CLog::Log(LOGDEBUG,"%s, received: bytes read report", __FUNCTION__); break; case PacketType.Control: HandlePing(packet); break; case PacketType.ServerBW: HandleServerBW(packet); break; case PacketType.ClientBW: HandleClientBW(packet); break; case PacketType.Audio: //CLog::Log(LOGDEBUG,"%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize); //HandleAudio(packet); if (m_mediaChannel == 0) m_mediaChannel = packet.m_nChannel; if (Pausing == 0) m_mediaStamp = packet.m_nTimeStamp; bHasMediaPacket = 1; break; case PacketType.Video: //CLog::Log(LOGDEBUG,"%s, received: video %lu bytes", __FUNCTION__, packet.m_nBodySize); //HandleVideo(packet); if (m_mediaChannel == 0) m_mediaChannel = packet.m_nChannel; if (Pausing == 0) m_mediaStamp = packet.m_nTimeStamp; bHasMediaPacket = 1; break; case PacketType.Metadata: //CLog::Log(LOGDEBUG,"%s, received: notify %lu bytes", __FUNCTION__, packet.m_nBodySize); HandleMetadata(packet); bHasMediaPacket = 1; break; case PacketType.Invoke: //CLog::Log(LOGDEBUG,"%s, received: invoke %lu bytes", __FUNCTION__, packet.m_nBodySize); if (HandleInvoke(packet) == true) bHasMediaPacket = 2; break; case PacketType.FlvTags: //Logger.Log(string.Format("received: FLV tag(s) {0} bytes", packet.m_nBodySize)); HandleFlvTags(packet); bHasMediaPacket = 1; break; default: Logger.Log(string.Format("Ignoring packet of type {0}", packet.PacketType)); break; } return bHasMediaPacket; }
bool SendPause(bool doPause) { RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x08; // we make 8 our stream channel packet.HeaderType = HeaderType.Medium; packet.PacketType = PacketType.Invoke; List<byte> enc = new List<byte>(); EncodeString(enc, "pause"); EncodeNumber(enc, ++m_numInvokes); enc.Add(0x05); // NULL EncodeBoolean(enc, doPause); EncodeNumber(enc, (double)m_channelTimestamp[m_mediaChannel]); packet.m_body = enc.ToArray(); packet.m_nBodySize = (uint)enc.Count; Logger.Log(string.Format("Sending pause: ({0}), Time = {1}", doPause.ToString(), m_channelTimestamp[m_mediaChannel])); return SendPacket(packet); }
void HandleClientBW(RTMPPacket packet) { m_nClientBW = ReadInt32(packet.m_body, 0); if (packet.m_nBodySize > 4) m_nClientBW2 = packet.m_body[4]; else m_nClientBW2 = 0; Logger.Log(string.Format("HandleClientBW: client BW = {0} {1}", m_nClientBW, m_nClientBW2)); }
/// <summary> /// The type of Ping packet is 0x4 and contains two mandatory parameters and two optional parameters. /// The first parameter is the type of Ping (short integer). /// The second parameter is the target of the ping. /// As Ping is always sent in Channel 2 (control channel) and the target object in RTMP header is always 0 /// which means the Connection object, /// it's necessary to put an extra parameter to indicate the exact target object the Ping is sent to. /// The second parameter takes this responsibility. /// The value has the same meaning as the target object field in RTMP header. /// (The second value could also be used as other purposes, like RTT Ping/Pong. It is used as the timestamp.) /// The third and fourth parameters are optional and could be looked upon as the parameter of the Ping packet. /// Below is an unexhausted list of Ping messages. /// type 0: Clear the stream. No third and fourth parameters. The second parameter could be 0. After the connection is established, a Ping 0,0 will be sent from server to client. The message will also be sent to client on the start of Play and in response of a Seek or Pause/Resume request. This Ping tells client to re-calibrate the clock with the timestamp of the next packet server sends. /// type 1: Tell the stream to clear the playing buffer. /// type 3: Buffer time of the client. The third parameter is the buffer time in millisecond. /// type 4: Reset a stream. Used together with type 0 in the case of VOD. Often sent before type 0. /// type 6: Ping the client from server. The second parameter is the current time. /// type 7: Pong reply from client. The second parameter is the time the server sent with his ping request. /// type 26: SWFVerification request /// type 27: SWFVerification response /// type 31: Buffer empty /// type 32: Buffer full /// </summary> /// <param name="nType"></param> /// <param name="nObject"></param> /// <param name="nTime"></param> /// <returns></returns> bool SendPing(short nType, uint nObject, uint nTime) { Logger.Log(string.Format("Sending ping type: {0}", nType)); RTMPPacket packet = new RTMPPacket(); packet.m_nChannel = 0x02; // control channel (ping) packet.HeaderType = HeaderType.Medium; packet.PacketType = PacketType.Control; //packet.m_nInfoField1 = System.Environment.TickCount; int nSize = (nType == 0x03 ? 10 : 6); // type 3 is the buffer time and requires all 3 parameters. all in all 10 bytes. if (nType == 0x1B) nSize = 44; packet.AllocPacket(nSize); packet.m_nBodySize = (uint)nSize; List<byte> buf = new List<byte>(); EncodeInt16(buf, nType); if (nType == 0x1B) { buf.AddRange(Link.SWFVerificationResponse); } else { if (nSize > 2) EncodeInt32(buf, (int)nObject); if (nSize > 6) EncodeInt32(buf, (int)nTime); } packet.m_body = buf.ToArray(); return SendPacket(packet, false); }
/// <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; }
bool WriteStream(RTMPPacket packet, Stream stream, out uint nTimeStamp) { nTimeStamp = 0; uint prevTagSize = 0; // skip video info/command packets if (packet.PacketType == PacketType.Video && packet.m_nBodySize == 2 && ((packet.m_body[0] & 0xf0) == 0x50)) { return true; } if (packet.PacketType == PacketType.Video && packet.m_nBodySize <= 5) { Logger.Log(string.Format("ignoring too small video packet: size: {0}", packet.m_nBodySize)); return true; } if (packet.PacketType == PacketType.Audio && packet.m_nBodySize <= 1) { Logger.Log(string.Format("ignoring too small audio packet: size: {0}", packet.m_nBodySize)); return true; } // audio (0x08), video (0x09) or metadata (0x12) packets : // construct 11 byte header then add rtmp packet's data if (packet.PacketType == PacketType.Audio || packet.PacketType == PacketType.Video || packet.PacketType == PacketType.Metadata) { nTimeStamp = (uint)packet.m_nTimeStamp; prevTagSize = 11 + packet.m_nBodySize; stream.WriteByte((byte)packet.PacketType); List<byte> somebytes = new List<byte>(); RTMP.EncodeInt24(somebytes, (int)packet.m_nBodySize); RTMP.EncodeInt24(somebytes, (int)nTimeStamp); stream.Write(somebytes.ToArray(), 0, somebytes.Count); somebytes.Clear(); stream.WriteByte((byte)(((nTimeStamp) & 0xFF000000) >> 24)); // stream id RTMP.EncodeInt24(somebytes, 0); stream.Write(somebytes.ToArray(), 0, somebytes.Count); somebytes.Clear(); // body stream.Write(packet.m_body, 0, (int)packet.m_nBodySize); // prevTagSize RTMP.EncodeInt32(somebytes, (int)prevTagSize); stream.Write(somebytes.ToArray(), 0, somebytes.Count); somebytes.Clear(); } // correct tagSize and obtain timestamp if we have an FLV stream else if (packet.PacketType == PacketType.FlvTags) { List<byte> data = packet.m_body.ToList(); uint pos = 0; /* grab first timestamp and see if it needs fixing */ nTimeStamp = (uint)RTMP.ReadInt24(packet.m_body, 4); nTimeStamp |= (uint)(packet.m_body[7] << 24); var delta = packet.m_nTimeStamp - nTimeStamp; while (pos + 11 < packet.m_nBodySize) { uint dataSize = (uint)RTMP.ReadInt24(packet.m_body, (int)pos + 1); // size without header (11) and without prevTagSize (4) nTimeStamp = (uint)RTMP.ReadInt24(packet.m_body, (int)pos + 4); nTimeStamp |= (uint)(packet.m_body[pos + 7] << 24); if (delta != 0) { nTimeStamp += delta; List<byte> newTimeStampData = new List<byte>(); RTMP.EncodeInt24(newTimeStampData, (int)nTimeStamp); data[(int)pos + 4] = newTimeStampData[0]; data[(int)pos + 5] = newTimeStampData[1]; data[(int)pos + 6] = newTimeStampData[2]; data[(int)pos + 7] = (byte)(nTimeStamp >> 24); } if (pos + 11 + dataSize + 4 > packet.m_nBodySize) { if (pos + 11 + dataSize > packet.m_nBodySize) { Logger.Log(string.Format("Wrong data size ({0}), stream corrupted, aborting!", dataSize)); return false; } // we have to append a last tagSize! prevTagSize = dataSize + 11; RTMP.EncodeInt32(data, (int)prevTagSize); } else { prevTagSize = (uint)RTMP.ReadInt32(packet.m_body, (int)(pos + 11 + dataSize)); if (prevTagSize != (dataSize + 11)) { //Tag and data size are inconsistent, writing tag size according to dataSize+11 prevTagSize = dataSize + 11; RTMP.EncodeInt32(data, (int)prevTagSize, pos + 11 + dataSize); } } pos += prevTagSize + 4;//(11+dataSize+4); } stream.Write(data.ToArray(), 0, data.Count); } return true; }