/// <summary> /// Writes a RtcpPacket to the dump. /// If written in Binary the packet will contain an 8 Byte overhead. If written in Payload or Header the Rtcp Packet is silently ignored. /// </summary> /// <param name="packet">The packet to write</param> /// <param name="timeOffset">The optional time the packet was recieved relative to the beginning of the file. If the packet has a Created time that will be used otherwise DateTime.UtcNow.</param> public void WritePacket(Rtcp.RtcpPacket packet, TimeSpan?timeOffset = null, System.Net.IPEndPoint source = null) { if (timeOffset < TimeSpan.Zero) { throw new ArgumentOutOfRangeException("timeOffset cannot be less than the start of the file which is defined in the header. "); } //Use the tool entry so it is disposed immediately using (var entry = new RtpToolEntry(m_Start, source ?? m_Source, packet)) { //If given a specific timeoffset use it /* start of recording (GMT) */ if (timeOffset.HasValue) { entry.Offset = (int)timeOffset.Value.TotalMilliseconds; } else { entry.Offset = (int)(DateTime.UtcNow - m_Start).TotalMilliseconds; //otherwise calulcate it } //entry.Offset = /* milliseconds since the start of recording */ //Write the item WriteToolEntry(entry); } }
//WritePackets(RtcpPacket[]) //WritePackets(RtpFrame frame) /// <summary> /// Writes a DumpItem to the underlying stream /// </summary> /// <param name="item">The DumpItem to write</param> internal void WriteToolEntry(RtpToolEntry entry) { //If already written nothing occurs WriteFileHeader(); //Write non text format entry if (m_Format < FileFormat.Text) { //This is a header style if (m_Format == FileFormat.Header) { if (entry.IsRtcp) { //Indicate only the header is kept entry.Length = (short)(entry.BlobLength = Media.Rtcp.RtcpHeader.Length + +RtpToolEntry.sizeOf_RD_packet_T); //Write the data from the blob using (Rtcp.RtcpPacket rtcp = new Rtcp.RtcpPacket(entry.Blob, entry.Pointer + RtpToolEntry.sizeOf_RD_packet_T)) m_Writer.Write(entry.Blob, 0, entry.BlobLength); } else if (m_Format != FileFormat.Rtcp) { using (Rtp.RtpPacket rtp = new Rtp.RtpPacket(entry.Blob, entry.Pointer + RtpToolEntry.sizeOf_RD_packet_T)) { //Indicate only the header is kept entry.Length = (short)(entry.BlobLength = Media.Rtp.RtpHeader.Length + RtpToolEntry.sizeOf_RD_packet_T); //Write the data from the blob m_Writer.Write(entry.Blob, 0, entry.BlobLength); } } } else if (m_Format == FileFormat.Binary) { //Nothing to change m_Writer.Write(entry.Blob); } else if (m_Format == FileFormat.Payload) { if (entry.IsRtcp) { using (Rtcp.RtcpPacket rtcp = new Rtcp.RtcpPacket(entry.Blob, entry.Pointer + RtpToolEntry.sizeOf_RD_packet_T)) { entry.Length = (short)(entry.BlobLength = rtcp.Payload.Count() + +RtpToolEntry.sizeOf_RD_packet_T); m_Writer.Write(rtcp.Payload.ToArray()); } } else { using (Rtp.RtpPacket rtp = new Rtp.RtpPacket(entry.Blob, entry.Pointer + RtpToolEntry.sizeOf_RD_packet_T)) { entry.Length = (short)(entry.BlobLength = rtp.PayloadData.Count() + RtpToolEntry.sizeOf_RD_packet_T); m_Writer.Write(entry.Blob, 0, entry.BlobLength); } } } } else { //Write the textual version of the entry m_Writer.Write(System.Text.Encoding.ASCII.GetBytes(entry.ToString(m_Format))); } //Increment for the entry written ++m_ItemsWritten; }
/// <summary> /// O ( ) /// </summary> public static void TestAConstructor_And_Reserialization() { //Cache a bitValue bool bitValue = false; unchecked { //Test every possible bit packed value that can be valid in the first and second octet for (int ibitValue = 0; ibitValue < 2; ++ibitValue) { //Make a bitValue after the 0th iteration if (ibitValue > 0) { bitValue = Convert.ToBoolean(ibitValue); } //Permute every possible value within the 2 bit Version for (byte VersionCounter = 0; VersionCounter <= Media.Common.Binary.TwoBitMaxValue; ++VersionCounter) { //Permute every possible value in the 7 bit PayloadCounter for (int PayloadCounter = 0; PayloadCounter <= byte.MaxValue; ++PayloadCounter) { //Permute every possible value in the 5 bit BlockCount for (byte ReportBlockCounter = byte.MinValue; ReportBlockCounter <= Media.Common.Binary.FiveBitMaxValue; ++ReportBlockCounter) { //Permute every possible value in the Padding field. for (byte PaddingCounter = byte.MinValue; PaddingCounter <= Media.Common.Binary.FiveBitMaxValue; ++PaddingCounter) { //Create a RtpPacket instance using the specified options using (Media.Rtcp.RtcpPacket p = new Rtcp.RtcpPacket(VersionCounter, PayloadCounter, PaddingCounter, 7, 0, ReportBlockCounter)) { //Check the Version System.Diagnostics.Debug.Assert(p.Version == VersionCounter, "Unexpected Version"); //Check the Padding System.Diagnostics.Debug.Assert(p.Padding == PaddingCounter > 0, "Unexpected Padding"); //Check the BlockCount System.Diagnostics.Debug.Assert(p.BlockCount == ReportBlockCounter, "Unexpected BlockCount"); //Check the SynchronizationSourceIdentifier System.Diagnostics.Debug.Assert(p.SynchronizationSourceIdentifier == 0, "Unexpected SynchronizationSourceIdentifier"); //Check the LengthInWordsMinusOne System.Diagnostics.Debug.Assert(p.Header.LengthInWordsMinusOne == 0, "Unexpected LengthInWordsMinusOne"); //Check the Length System.Diagnostics.Debug.Assert(p.Length == Media.Rtcp.RtcpHeader.Length + PaddingCounter, "Unexpected Length"); //Check the IsComplete System.Diagnostics.Debug.Assert(p.IsComplete, "Not Complete"); //Check the result of serialization using padding. //Another test would be to permute every possible value for the LengthInWords field :) //Set the LengthInWordsMinusOne so the correct amount of bytes are serialzed, we specified 0 in the constructor //p.SetLengthInWordsMinusOne(); byte[] serialized = p.Prepare().ToArray(); System.Diagnostics.Debug.Assert(serialized.Length == p.Length, "Unexpected Binary Data Serialized"); //Make a managed packet from the serialized data and re-verify using (Media.Rtcp.RtcpPacket s = new Rtcp.RtcpPacket(serialized, 0)) { //Check the IsComplete System.Diagnostics.Debug.Assert(s.IsComplete, "Not Complete"); //Check the Version System.Diagnostics.Debug.Assert(s.Version == VersionCounter, "Unexpected Version"); //Check the Padding System.Diagnostics.Debug.Assert(p.PaddingOctets == PaddingCounter && s.Padding == PaddingCounter > 0, "Unexpected Padding"); //Check the BlockCount System.Diagnostics.Debug.Assert(s.BlockCount == ReportBlockCounter, "Unexpected BlockCount"); //Check the SynchronizationSourceIdentifier System.Diagnostics.Debug.Assert(s.SynchronizationSourceIdentifier == 0, "Unexpected SynchronizationSourceIdentifier"); //Check the LengthInWordsMinusOne System.Diagnostics.Debug.Assert(s.Header.LengthInWordsMinusOne == p.Header.LengthInWordsMinusOne, "Unexpected LengthInWordsMinusOne"); //Check the Length System.Diagnostics.Debug.Assert(s.Length == p.Length, "Unexpected Length"); System.Diagnostics.Debug.Assert(s.Prepare().SequenceEqual(serialized), "Unexpected Binary Data Serialized"); } } } } } } } } }
//Move to RtpSendExtensions? /// <summary> /// Parses the data contained in the given <see cref="System.IO.BinaryReader"/> for data which corresponds to a format compatible with <see href="http://www.cs.columbia.edu/irt/software/rtptools/#rtpsend">rtpsend</see>. /// If a Binary format is encoutered (by the presence of the "#!rtpplay1.0" file header then <see cref="RtpTools.RtpDump.RtpDumpExtensions.ReadBinaryToolEntry"/> will be called implicitly with the reader given, /// In such a case, unexpected will contain the data which matched the file header. /// </summary> /// <param name="reader">The <see cref="System.IO.BinaryReader"/> which should be created using <see cref="System.Text.Encoding.ASCII"/></param> /// <param name="format">The format found while parsing the description.</param> /// <param name="unexpected">Will only contain data if format was unknown, and contains the data encountered in the stream while attempting to parse.</param> /// <returns>The item which was created as a result of reading from the stream.</returns> internal static RtpToolEntry ParseText(System.IO.BinaryReader reader, System.Net.IPEndPoint source, ref FileFormat format, out byte[] unexpected) { unexpected = null; double timeOffset = 0; System.Net.IPEndPoint sourceInfo = source; long position = reader.BaseStream.Position; Common.IPacket builtPacket = null; //Keep track of making a Rtp or Rtcp entry. bool rtp = false; Rtp.RtpPacket rtpP = null; Rtcp.RtcpPacket rtcP = null; ///<summary> /// each entry starts with a time value, in seconds, relative to the beginning of the trace. /// The time value must appear at the beginning of a line, without white space. Within an RTP or RTCP packet description, /// parameters may appear in any order, without white space around the equal sign. /// Lines are continued with initial white space on the next line. /// Comment lines start with #. Strings are enclosed in quotation marks. /// <see cref="Tokens"/> ///</summary> //The amount of tokens consumed from the reader, where a token is defined as above int tokensParsed = -1, //The amount of bytes read lineBytesLength = 0, //Used for token parsing, the index of the recognized token in 'Tokens' tokenIndex = -1; //Indicates if in a comment bool parsingCommentOrWhitespace = false; //Contains the data read from the stream until '\n' occurs. byte[] lineBytes; //Indicates if the parsing of the entry is complete bool doneParsing = false, formatUnknown = format == FileFormat.Unknown, needAnyToken = true; //A string instance which was used to compare to known `Tokens` string token; //No bytes have actually been consumed from the stream yet, while not done parsing and not at the end of the stream while (!doneParsing && reader.BaseStream.Position < reader.BaseStream.Length) { //Determine the following character (ASCIIEncoding SHOULD have been specified in creation of the reader) [If not could possibly have also found out without consuming a byte here] int peek = reader.PeekChar(); //If no data can be read if (peek == -1) { //then indiate an unknown format and then return null format = FileFormat.Unknown; return null; } else if (peek == RtpDump.RtpDumpExtensions.Hash || peek == (char)Common.ASCII.Space) { //Comment lines start with # (Hash). Strings are enclosed in quotation marks. parsingCommentOrWhitespace = true; //Could be a binary format however.... } else if (tokensParsed > 0 && peek == 'r' || peek == Common.ASCII.R) //Don't read any further a new entry follows (Could be a malformed entry with rXXX=YYY\n) { //doneParsing = true; break; } //Read until '\n' occurs RtpSendExtensions.ReadLineFeed(reader.BaseStream, out lineBytes); //Keep track of the amount of bytes read lineBytesLength = lineBytes.Length; //If nothing was read return if (lineBytesLength == 0) return null; //If the format is unknown then if (formatUnknown) { //check for the Binary format at the known ordinal, if found if (lineBytesLength > 2 && lineBytes[0] == RtpDump.RtpDumpExtensions.Hash && lineBytes[1] == RtpDump.RtpDumpExtensions.Bang) { //Indicate a binary format so far format = FileFormat.Binary; //give the `unexpected` data back to the caller, which consisted of the header unexpected = lineBytes; //Remove the reference to the token now token = null; //Return the result of reading the binary entry. return null; } //Check for the short form before parsing a token //Search for '=' tokenIndex = Array.IndexOf<byte>(lineBytes, Common.ASCII.EqualsSign); //If not found then this must be a Short entry. if (tokenIndex == -1) { //No longer unknown because, formatUnknown = false; //This seems to be Short format format = FileFormat.Short; } else //There was a '=' sign { //No longer still unknown because, //This seems to be a Text format format = FileFormat.Text; //but we need to consume tokens until data in tokens as they occur to be sure } } //If not found then this must be a Short entry. if (format == FileFormat.Short) return RtpToolEntry.CreateShortEntry(DateTime.UtcNow.Subtract(TimeSpan.FromMilliseconds(timeOffset)), sourceInfo, lineBytes, 0, position); else if (parsingCommentOrWhitespace)//If parsing a whitespace or a comment { //Not parsing the comment / whitespace any more parsingCommentOrWhitespace = false; //Perform another iteration continue; } else if (needAnyToken) //If a token is needed { //Extract all tokens from the lineBytes string[] tokens = Encoding.ASCII.GetString(lineBytes).Split((char)Common.ASCII.Space, (char)Common.ASCII.EqualsSign, '(', ')'); //Any hex data will need a length, and we start at offset 0. int dataLen = 0, tokenOffset = 0; //If nothing was tokenized then return the unexpected data. if (tokens.Length == 0) { unexpected = lineBytes; return null; } //The first token must be a timeOffset if (timeOffset == 0) timeOffset = double.Parse(tokens[tokenOffset++]); //For each token in the tokens after the timeOffset for (int e = tokens.Length; tokenOffset < e; ++tokenOffset) { //Get the token at the index token = tokens[tokenOffset]; //Determine what to do based on if there was a matching token in the Tokens array. tokenIndex = Array.IndexOf<string>(Tokens, token); //The entry must be finished. if (-1 == tokenIndex) { unexpected = lineBytes; break; } //Increment for a token parsed within the entry so far ++tokensParsed; //Switch out logic based on token switch (tokenIndex) { //RTP case 1: { //The created structure will have a packetLength = 8 + dataLen rtp = true; rtpP = new Rtp.RtpPacket(0, false, false, Media.Common.MemorySegment.EmptyBytes); builtPacket = rtpP; //Do another iteration continue; } //RTCP case 17: { rtcP = new Rtcp.RtcpPacket(0, 0, 0, 0, 0, 0); //The created structure will have packetLen = 0 to indicate Rtcp. builtPacket = rtcP; //Do another iteration continue; } case 35: //from { string[] parts = tokens[++tokenOffset].Split((char)Common.ASCII.Colon); System.Net.IPAddress ip = System.Net.IPAddress.Parse(parts[0]); int port = int.Parse(parts[1]); System.Diagnostics.Debug.WriteLine(ip + " " + port); sourceInfo = new System.Net.IPEndPoint(ip, port); continue; } case 2: { //version int version = int.Parse(tokens[++tokenOffset]); System.Diagnostics.Debug.WriteLine(version); if (rtp) rtpP.Header.Version = version; else rtcP.Header.Version = version; continue; } case 3: //p { int paddingCount = int.Parse(tokens[++tokenOffset]); System.Diagnostics.Debug.WriteLine(paddingCount); break; } case 4: //x { if (rtp) { bool hasExtension = int.Parse(tokens[++tokenOffset]) == 1; System.Diagnostics.Debug.WriteLine(hasExtension); rtpP.Header.Extension = hasExtension; } break; } case 5: //m { if (rtp) { bool hasMarker = int.Parse(tokens[++tokenOffset]) == 1; System.Diagnostics.Debug.WriteLine(hasMarker); rtpP.Header.Marker = hasMarker; } break; } case 6: //pt { int payloadType = int.Parse(tokens[++tokenOffset]); System.Diagnostics.Debug.WriteLine(payloadType); if (rtp) rtpP.Header.PayloadType = payloadType; else rtcP.Header.PayloadType = payloadType; break; } case 7: //ts { if (rtp) { int ts = int.Parse(tokens[++tokenOffset]); System.Diagnostics.Debug.WriteLine(ts); rtpP.Header.Timestamp = ts; } break; } case 8: //seq { if (rtp) { int seq = int.Parse(tokens[++tokenOffset]); System.Diagnostics.Debug.WriteLine(seq); rtpP.Header.SequenceNumber = seq; } break; } case 9: //ssrc { token = tokens[++tokenOffset]; token = token.Remove(token.Length - 1).Replace(HexSpecifier, string.Empty); int ssrc = 0; if (!int.TryParse(token, out ssrc)) //plain int ssrc = int.Parse(token, System.Globalization.NumberStyles.HexNumber);//hex System.Diagnostics.Debug.WriteLine(ssrc); if (rtp) rtpP.Header.SynchronizationSourceIdentifier = ssrc; else rtcP.Header.SendersSynchronizationSourceIdentifier = ssrc; break; } case 10: //cc { int cc = int.Parse(tokens[++tokenOffset]); System.Diagnostics.Debug.WriteLine(cc); if (rtp) rtpP.Header.ContributingSourceCount = cc; else rtcP.Header.BlockCount = cc; break; } case 11: //csrc { int csrc = int.Parse(tokens[++tokenOffset]); System.Diagnostics.Debug.WriteLine(csrc); //Add to a list. break; } case 12://data { //Token based reading may not be required anymore needAnyToken = false; //Not unknown anymore because, formatUnknown = false; //Definitely hex format format = FileFormat.Hex; //The next token is the string in hex format of the payload. ++tokenIndex; //If it begins with Hash then its NIL //Done parsing the entry. doneParsing = true; continue; } case 13: //ext_type { int ext_type = int.Parse(tokens[++tokenOffset]); System.Diagnostics.Debug.WriteLine(ext_type); break; } case 14: //ext_len { int ext_len = int.Parse(tokens[++tokenOffset]); System.Diagnostics.Debug.WriteLine(ext_len); break; } case 15: //ext_data { //The next token is the string in hex format of the payload. ++tokenIndex; //If it begins with Hash then its NIL break; } case 16: //len { dataLen = int.Parse(tokens[++tokenOffset]); System.Diagnostics.Debug.WriteLine(dataLen); continue; } default: { //If the format was unknown if (formatUnknown) { //It is no longer so because, formatUnknown = false; //it is no longer unknown, it is definitely a Text format format = FileFormat.Text; } break; } }//Done determining what to do with a token }//Done with tokens }//Don't need to parse any tokens } //Create the resulting entry with the data contained in memory read from the reader by the writer return new RtpToolEntry(DateTime.UtcNow.Subtract(TimeSpan.FromMilliseconds(timeOffset)), sourceInfo, builtPacket, (int)timeOffset, position); }
/// <summary> /// O ( ) /// </summary> public static void TestAConstructor_And_Reserialization() { //Cache a bitValue bool bitValue = false; unchecked { //Test every possible bit packed value that can be valid in the first and second octet for (int ibitValue = 0; ibitValue < 2; ++ibitValue) { //Make a bitValue after the 0th iteration if (ibitValue > 0) bitValue = Convert.ToBoolean(ibitValue); //Permute every possible value within the 2 bit Version for (byte VersionCounter = 0; VersionCounter <= Media.Common.Binary.TwoBitMaxValue; ++VersionCounter) { //Permute every possible value in the 7 bit PayloadCounter for (int PayloadCounter = 0; PayloadCounter <= byte.MaxValue; ++PayloadCounter) { //Permute every possible value in the 5 bit BlockCount for (byte ReportBlockCounter = byte.MinValue; ReportBlockCounter <= Media.Common.Binary.FiveBitMaxValue; ++ReportBlockCounter) { //Permute every possible value in the Padding field. for (byte PaddingCounter = byte.MinValue; PaddingCounter <= Media.Common.Binary.FiveBitMaxValue; ++PaddingCounter) { //Create a RtpPacket instance using the specified options using (Media.Rtcp.RtcpPacket p = new Rtcp.RtcpPacket(VersionCounter, PayloadCounter, PaddingCounter, 7, 0, ReportBlockCounter)) { //Check the Version System.Diagnostics.Debug.Assert(p.Version == VersionCounter, "Unexpected Version"); //Check the Padding System.Diagnostics.Debug.Assert(p.Padding == PaddingCounter > 0, "Unexpected Padding"); //Check the BlockCount System.Diagnostics.Debug.Assert(p.BlockCount == ReportBlockCounter, "Unexpected BlockCount"); //Check the SynchronizationSourceIdentifier System.Diagnostics.Debug.Assert(p.SynchronizationSourceIdentifier == 0, "Unexpected SynchronizationSourceIdentifier"); //Check the LengthInWordsMinusOne System.Diagnostics.Debug.Assert(p.Header.LengthInWordsMinusOne == 0, "Unexpected LengthInWordsMinusOne"); //Check the Length System.Diagnostics.Debug.Assert(p.Length == Media.Rtcp.RtcpHeader.Length + PaddingCounter, "Unexpected Length"); //Check the IsComplete System.Diagnostics.Debug.Assert(p.IsComplete, "Not Complete"); //Check the result of serialization using padding. //Another test would be to permute every possible value for the LengthInWords field :) //Set the LengthInWordsMinusOne so the correct amount of bytes are serialzed, we specified 0 in the constructor //p.SetLengthInWordsMinusOne(); byte[] serialized = p.Prepare().ToArray(); System.Diagnostics.Debug.Assert(serialized.Length == p.Length, "Unexpected Binary Data Serialized"); //Make a managed packet from the serialized data and re-verify using (Media.Rtcp.RtcpPacket s = new Rtcp.RtcpPacket(serialized, 0)) { //Check the IsComplete System.Diagnostics.Debug.Assert(s.IsComplete, "Not Complete"); //Check the Version System.Diagnostics.Debug.Assert(s.Version == VersionCounter, "Unexpected Version"); //Check the Padding System.Diagnostics.Debug.Assert(p.PaddingOctets == PaddingCounter && s.Padding == PaddingCounter > 0, "Unexpected Padding"); //Check the BlockCount System.Diagnostics.Debug.Assert(s.BlockCount == ReportBlockCounter, "Unexpected BlockCount"); //Check the SynchronizationSourceIdentifier System.Diagnostics.Debug.Assert(s.SynchronizationSourceIdentifier == 0, "Unexpected SynchronizationSourceIdentifier"); //Check the LengthInWordsMinusOne System.Diagnostics.Debug.Assert(s.Header.LengthInWordsMinusOne == p.Header.LengthInWordsMinusOne, "Unexpected LengthInWordsMinusOne"); //Check the Length System.Diagnostics.Debug.Assert(s.Length == p.Length, "Unexpected Length"); System.Diagnostics.Debug.Assert(s.Prepare().SequenceEqual(serialized), "Unexpected Binary Data Serialized"); } } } } } } } } }