public Common.SegmentStream GetSample(Container.Track track, out TimeSpan duration) { Rtp.RtpFrame result = new Rtp.RtpFrame(0); while (HasNext) { Rtp.RtpPacket next = new Rtp.RtpPacket(ReadNext().Data.ToArray(), 0); if (result.Count > 0 && next.Timestamp != result.Timestamp) { break; } result.Add(next); if (next.Marker) { break; } } duration = TimeSpan.FromMilliseconds(90 * result.Count); //return result.Assemble().ToArray(); result.Depacketize(); return(result.Buffer); }
protected internal override void ProcessPacket(Rtp.RtpPacket packet, bool ignoreForbiddenZeroBit = true, bool fullStartCodes = false) { //Determine if the forbidden bit is set and the type of nal from the first byte byte firstByte = packet.Payload[packet.HeaderOctets]; byte nalUnitType = (byte)(firstByte & Common.Binary.FiveBitMaxValue); //Determine if extended nals are present switch (nalUnitType) { case Media.Codecs.Video.H264.NalUnitType.Prefix: //Prefix Nal { return; } case Media.Codecs.Video.H264.NalUnitType.SequenceParameterSetSubset: // Subset sequence parameter set { return; } case Media.Codecs.Video.H264.NalUnitType.SliceExtension: // Coded slice in scalable extension { return; } case Media.Codecs.Video.H264.NalUnitType.PayloadContentScalabilityInformation: // PACSI NAL unit { //if more than 10 bytes present containesSei = true. //Skip 10 bytes //read nal unit size //Write nal unit size. return; } case Media.Codecs.Video.H264.NalUnitType.NonInterleavedMultiTimeAggregation: // Empty, NT-MTAP etc. { //Get subType //Read nal unit size //skip TS offset (2 bytes) //Skip DON if present return; } default: { //Handle as per RFC6184 base.ProcessPacket(packet, ignoreForbiddenZeroBit, fullStartCodes); return; } } }
public string ToTextualConvention(FileFormat?format = null) { try { StringBuilder sb = new StringBuilder(); var ts = Timebase.TimeOfDay.Add(TimeSpan.FromMilliseconds(Offset)); if (IsRtcp) { sb.Append(RtpSend.ToTextualConvention(format ?? Format, Media.Rtcp.RtcpPacket.GetPackets(Blob, Pointer + sizeOf_RD_packet_T, BlobLength - sizeOf_RD_packet_T), ts, Source)); } else { using (var rtp = new Rtp.RtpPacket(Blob, Pointer + sizeOf_RD_packet_T)) sb.Append(RtpSend.ToTextualConvention(format ?? Format, rtp, ts, Source)); } return(sb.ToString()); } catch { throw; } }
/// <summary> /// Writes the given RtpPacket to the stream at the current position. /// If written in <see cref="DumpFormat.Binary"/> or <see cref="DumpFormat.Header"/> the packet will contain an 8 Byte overhead. /// If written in <see cref="DumpFormat.Header"/> the packet will not contain the RtpPacket Payload or the Extension Data if present. /// If written in <see cref="DumpFormat.Payload"/> the Rtp Packet will only contain the RTP Payload and will not be able to be read back into RtpPackets with this class. /// </summary> /// <param name="packet">The time</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(Rtp.RtpPacket 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 if (timeOffset.HasValue) { entry.Offset = (int)timeOffset.Value.TotalMilliseconds; } else { entry.Offset = (int)(DateTime.UtcNow - m_Start).TotalMilliseconds; //otherwise calulcate it } //Write the item WriteToolEntry(entry); } }
//Should the ObjectType be allowed to be given...? 3 or 5 for Audio? //No longer needed /// <summary> /// Write the required prerequesite data to the Buffer along with the Video Frame. /// </summary> /// <param name="profileLevelId">The profileLevelId (if not 1)</param> //public void Depacketize(byte profileLevelId = 1) //{ // DisposeBuffer(); // if (profileLevelId < 1) throw new ArgumentException("Must be a valid Mpeg 4 Profile Level Id with a value >= 1", "profileLevelId"); // /* // Example usages for the "profile-level-id" parameter are: // 1 : MPEG-4 Visual Simple Profile/Level 1 // 15 : AAC? // 34 : MPEG-4 Visual Core Profile/Level 2 // 145: MPEG-4 Visual Advanced Real Time Simple Profile/Level 1 // */ // m_Buffer = new MemoryStream(Media.Containers.Mpeg.StartCodes.StartCodePrefix. //00 00 01 // Concat(Media.Common.Extensions.Linq.LinqExtensions.Yield(Media.Codecs.Video.Mpeg4.StartCodes.VisualObjectSequence)). // Concat(Media.Common.Extensions.Linq.LinqExtensions.Yield(profileLevelId)). // B0 XX (ID) // Concat(Media.Containers.Mpeg.StartCodes.StartCodePrefix).Concat(Assemble()).ToArray()); // 00 00 01 XX (DATA) //} public virtual void ProcessPacket(Rtp.RtpPacket packet, byte profileLevelId = 1) { int addIndex = packet.Timestamp - packet.SequenceNumber; // Depacketized.Count > 0 ? Depacketized.Keys.Last() : 0; //byte[] t = new byte[] { profileLevelId, Media.Codecs.Video.Mpeg4.StartCodes.VisualObjectSequence }; //Common.MemorySegment a = new Common.MemorySegment(t, 0, 1); //Common.MemorySegment b = new Common.MemorySegment(t, 1, 1); //Creates 4 byte array each time with the first 3 bytes being the same, would need a way to combine MemorySegments for this to be any more efficient. Depacketized.Add(addIndex++, CreatePrefixedStartCodeSegment(Media.Codecs.Video.Mpeg4.StartCodes.VisualObjectSequence)); //Depacketized.Add(addIndex++, StartCodePrefixSegment); //Depacketized.Add(addIndex++, b); //Or //Depacketized.Add(addIndex++, StartCodePrefixSegment); //Depacketized.Add(addIndex++, new Common.MemorySegment(new byte[] { Media.Codecs.Video.Mpeg4.StartCodes.VisualObjectSequence })); //What a waste, 4 bytes + to describe 1 Depacketized.Add(addIndex++, new Common.MemorySegment(new byte[] { profileLevelId })); Depacketized.Add(addIndex++, StartCodePrefixSegment); // or //Depacketized.Add(addIndex++, a); //Depacketized.Add(addIndex++, StartCodePrefixSegment); Depacketized.Add(addIndex++, packet.PayloadDataSegment); }
//SUpparioucly thought that the last frame would remain in a not final state so one should check that the Goodbye has not been received when processing the event... //Shows what happpens when packets get out of order too badely.. public void TestFrameChangedEvents() { using (Rtp.RtpClient rtpClient = new Rtp.RtpClient()) { //Fire Frames with single packets and order = 3, 1, 2. //Each have marker //What is the expected order(3 final, 1 final, 2 final)? and count (3)! int count = 0, fcount = 0; Rtp.RtpClient.TransportContext tc = new Rtp.RtpClient.TransportContext(0, 1, 0); //Needed to resolve packet payload and avoid exception when validating packet... :( tc.MediaDescription = new Sdp.MediaDescription(Sdp.MediaType.unknown, 0, "RTP", 0); rtpClient.AddContext(tc); rtpClient.FrameChangedEventsEnabled = true; rtpClient.RtpFrameChanged += (s, z, t, f) => { ++count; if (f) { ++fcount; } Console.WriteLine("RtpFrameChanged=>" + z.HighestSequenceNumber + z.HasMarker + f); }; Console.WriteLine("Count: " + count); Console.WriteLine("Final Count: " + fcount); int tests = 4; Rtp.RtpPacket rtpPacket = new Rtp.RtpPacket(12) { Timestamp = tests * 1000, SequenceNumber = tests, Marker = true }; for (int i = tests; i >= 0; --i) { rtpPacket.Timestamp -= tests * 1000; rtpPacket.SequenceNumber = i; rtpClient.HandleIncomingRtpPacket(null, rtpPacket, tc); } //8 Frame Changes only 3 are final... if (count != tests || fcount != tests) { throw new Exception(); } count = fcount = 0; rtpPacket.SequenceNumber = 4; rtpPacket.Timestamp = 4000; rtpClient.HandleIncomingRtpPacket(null, rtpPacket, tc); rtpPacket.SequenceNumber = 1; rtpPacket.Timestamp = 1000; rtpClient.HandleIncomingRtpPacket(null, rtpPacket, tc); rtpPacket.SequenceNumber = 3; rtpPacket.Timestamp = 3000; rtpClient.HandleIncomingRtpPacket(null, rtpPacket, tc); rtpPacket.SequenceNumber = 2; rtpPacket.Timestamp = 2000; rtpClient.HandleIncomingRtpPacket(null, rtpPacket, tc); if (count != tests || fcount != tests) { throw new Exception(); } } }
//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; }
//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); }
public static void TestAConstructor_And_Reserialization() { //Cache a bitValue bool bitValue = false; //Permute 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(bitValue); } //Permute every possible value within the 2 bit Version for (int VersionCounter = 0; VersionCounter <= Media.Common.Binary.TwoBitMaxValue; ++VersionCounter) { //Permute every possible value in the 7 bit PayloadCounter for (int PayloadCounter = 0; PayloadCounter <= sbyte.MaxValue; ++PayloadCounter) { //Permute every possible value in the 4 bit ContributingSourceCounter for (byte ContributingSourceCounter = byte.MinValue; ContributingSourceCounter <= Media.Common.Binary.FourBitMaxValue; ++ContributingSourceCounter) { int RandomId = Utility.Random.Next(), RandomSequenceNumber = Utility.Random.Next(ushort.MinValue, ushort.MaxValue), RandomTimestamp = Utility.Random.Next(); //Create a RtpPacket instance using the specified options using (Media.Rtp.RtpPacket p = new Rtp.RtpPacket(VersionCounter, bitValue, !bitValue, bitValue, PayloadCounter, ContributingSourceCounter, RandomId, RandomSequenceNumber, RandomTimestamp)) { //Check the Version System.Diagnostics.Debug.Assert(p.Version == VersionCounter, "Unexpected Version"); //Check the Padding System.Diagnostics.Debug.Assert(p.Padding == bitValue, "Unexpected Padding"); //Check the Extension System.Diagnostics.Debug.Assert(p.Extension == !bitValue, "Unexpected Extension"); //Check the PayloadType System.Diagnostics.Debug.Assert(p.PayloadType == PayloadCounter, "Unexpected PayloadType"); //Check the ContributingSourceCount System.Diagnostics.Debug.Assert(p.ContributingSourceCount == ContributingSourceCounter, "Unexpected ContributingSourceCounter"); //Check the Length System.Diagnostics.Debug.Assert(p.Length == Media.Rtp.RtpHeader.Length, "Unexpected Length"); //Serialize, Deserialize and verify again using (Media.Rtp.RtpPacket s = new Rtp.RtpPacket(p.Prepare().ToArray(), 0)) { if (false == s.Prepare().SequenceEqual(p.Prepare())) { throw new Exception("Unexpected Data"); } } } } } } } }
//Could be called Depacketize //Virtual because the RFC6190 logic defers to this method for non SVC nal types. /// <summary> /// Depacketizes a single packet. /// </summary> /// <param name="packet"></param> /// <param name="containsSps"></param> /// <param name="containsPps"></param> /// <param name="containsSei"></param> /// <param name="containsSlice"></param> /// <param name="isIdr"></param> internal protected virtual void ProcessPacket(Rtp.RtpPacket packet, bool ignoreForbiddenZeroBit = true, bool fullStartCodes = false) { //If the packet is null or disposed then do not process it. if (Common.IDisposedExtensions.IsNullOrDisposed(packet)) { return; } //Just put the packets into Depacketized at the end for most cases. int packetKey = Depacketized.Count; //The packets are not stored by SequenceNumber in Depacketized, they are stored in whatever Decoder Order is necessary. //Already contained. (Might want to wait for the Decoder Order Number to be checked. //if (Depacketized.ContainsKey(packetKey)) return; //(May need to handle re-ordering) //In such cases this step needs to place the packets into a seperate collection for sorting on DON / TSOFFSET before writing to the buffer. //From the beginning of the data in the actual payload int offset = packet.Payload.Offset + packet.HeaderOctets, padding = packet.PaddingOctets, count = (packet.Payload.Count - padding), end = packet.Length - padding; //Must have at least 2 bytes (When nalUnitType is a FragmentUnit.. 3) if (count <= 2 || offset > packet.Payload.Count) { return; } //Obtain the data of the packet with respect to extensions and csrcs present. byte[] packetData = packet.Payload.Array; //Determine if the forbidden bit is set and the type of nal from the first byte byte firstByte = packetData[offset]; //Should never be set... (unless decoding errors are present) if (false == ignoreForbiddenZeroBit && ((firstByte & 0x80) >> 7) != 0) { return; //throw new Exception("forbiddenZeroBit"); } byte nalUnitType = (byte)(firstByte & Common.Binary.FiveBitMaxValue); //RFC6184 @ Page 20 //o The F bit MUST be cleared if all F bits of the aggregated NAL units are zero; otherwise, it MUST be set. //if (forbiddenZeroBit && nalUnitType <= 23 && nalUnitType > 29) throw new InvalidOperationException("Forbidden Zero Bit is Set."); //Needs other state to check if previously F was set or not //Media.Codecs.Video.H264.NalUnitPriority priority = (Media.Codecs.Video.H264.NalUnitPriority)((firstByte & 0x60) >> 5); //Determine what to do switch (nalUnitType) { //Reserved - Ignore case Media.Codecs.Video.H264.NalUnitType.Unknown: case Media.Codecs.Video.H264.NalUnitType.PayloadContentScalabilityInformation: case Media.Codecs.Video.H264.NalUnitType.Reserved: { //May have 4 byte NAL header. //Do not handle return; } case Media.Codecs.Video.H264.NalUnitType.SingleTimeAggregationA: //STAP - A case Media.Codecs.Video.H264.NalUnitType.SingleTimeAggregationB: //STAP - B case Media.Codecs.Video.H264.NalUnitType.MultiTimeAggregation16: //MTAP - 16 case Media.Codecs.Video.H264.NalUnitType.MultiTimeAggregation24: //MTAP - 24 { //Move to Nal Data ++offset; //Todo Determine if need to Order by DON first. //EAT DON for ALL BUT STAP - A if (nalUnitType != Media.Codecs.Video.H264.NalUnitType.SingleTimeAggregationA) { //Should check for 2 bytes. //Read the DecoderOrderingNumber and add the value from the index. packetKey = Common.Binary.ReadU16(packetData, ref offset, BitConverter.IsLittleEndian); //If the number was already observed skip this packet. //if (Depacketized.ContainsKey(packetKey)) return; } //Should check for 2 bytes. //Consume the rest of the data from the packet while (offset < count) // + 2 <= { //Determine the nal unit size which does not include the nal header int tmp_nal_size = Common.Binary.Read16(packetData, ref offset, BitConverter.IsLittleEndian); //Should check for tmp_nal_size > 0 //If the nal had data and that data is in this packet then write it if (tmp_nal_size >= 0) { //For DOND and TSOFFSET switch (nalUnitType) { case Media.Codecs.Video.H264.NalUnitType.MultiTimeAggregation16: // MTAP - 16 (May require re-ordering) { //Should check for 3 bytes. //DOND 1 byte //Read DOND and TSOFFSET, combine the values with the existing index packetKey = (int)Common.Binary.ReadU24(packetData, ref offset, BitConverter.IsLittleEndian); //If the number was already observed skip this packet. //if (Depacketized.ContainsKey(packetKey)) return; goto default; } case Media.Codecs.Video.H264.NalUnitType.MultiTimeAggregation24: // MTAP - 24 (May require re-ordering) { //Should check for 4 bytes. //DOND 2 bytes //Read DOND and TSOFFSET , combine the values with the existing index packetKey = (int)Common.Binary.ReadU32(packetData, ref offset, BitConverter.IsLittleEndian); //If the number was already observed skip this packet. //if (Depacketized.ContainsKey(packetKey)) return; goto default; } default: { //Should check for tmp_nal_size > 0 //Could check for extra bytes or emulation prevention //https://github.com/raspberrypi/userland/blob/master/containers/rtp/rtp_h264.c //(Stores the nalType) Write the start code DepacketizeStartCode(ref packetKey, ref packetData[offset], fullStartCodes); //Add the depacketized data and increase the index. //Ensure the size is within the count. //When tmp_nal_size is 0 packetData which is referenced by this segment which will have a 0 count. Depacketized.Add(packetKey++, new Common.MemorySegment(packetData, offset, Common.Binary.Min(tmp_nal_size, count - offset))); //Move the offset past the nal offset += tmp_nal_size; continue; } } } } //No more data in packet. return; } case Media.Codecs.Video.H264.NalUnitType.FragmentationUnitA: //FU - A case Media.Codecs.Video.H264.NalUnitType.FragmentationUnitB: //FU - B (May require re-ordering) { /* * Informative note: When an FU-A occurs in interleaved mode, it * always follows an FU-B, which sets its DON. * Informative note: If a transmitter wants to encapsulate a single * NAL unit per packet and transmit packets out of their decoding * order, STAP-B packet type can be used. */ //Needs atleast 2 bytes to reconstruct... //3 bytes for any valid data to follow after the header. if (count >= 2) { //Offset still at the firstByte (FU Indicator) move to and read FU Header byte FUHeader = packetData[++offset]; bool Start = ((FUHeader & 0x80) >> 7) > 0; //https://tools.ietf.org/html/rfc6184 page 31... //A fragmented NAL unit MUST NOT be transmitted in one FU; that is, the //Start bit and End bit MUST NOT both be set to one in the same FU //header. //bool End = ((FUHeader & 0x40) >> 6) > 0; //ignoreReservedBit //bool Reserved = (FUHeader & 0x20) != 0; //Should not be set //if (Reserved) throw new InvalidOperationException("Reserved Bit Set"); //Move to data (Just read the FU Header) ++offset; //packet.SequenceNumber - packet.Timestamp; //Store the DecoderingOrderNumber we will derive from the timestamp and sequence number. //int DecodingOrderNumber = packetKey; //DON Present in FU - B, add the DON to the DecodingOrderNumber if (nalUnitType == Media.Codecs.Video.H264.NalUnitType.FragmentationUnitB) { //Needs 2 more bytes... Common.Binary.ReadU16(packetData, ref offset, BitConverter.IsLittleEndian); //offset += 2; } //Should verify count... just consumed 1 - 3 bytes and only required 2. //Determine the fragment size int fragment_size = count - offset; //Don't emit empty fragments //if (fragment_size == 0) return; //If the start bit was set if (Start) { //ignoreEndBit //if (End) throw new InvalidOperationException("Start and End Bit Set in same FU"); //Reconstruct the nal header //Use the first 3 bits of the first byte and last 5 bites of the FU Header byte nalHeader = (byte)((firstByte & 0xE0) | (FUHeader & Common.Binary.FiveBitMaxValue)); //(Stores the nal) Write the start code DepacketizeStartCode(ref packetKey, ref nalHeader, fullStartCodes); //Wasteful but there is no other way to provide this byte since it is constructor from two values in the header //Unless of course a FragmentHeader : MemorySegment was created, which could have a NalType property ... //Could also just have an overload which writes the NalHeader //Would need a CreateNalSegment static method with option for full (4 + 1) or short code ( 3 + 1)/ Depacketized.Add(packetKey++, new Common.MemorySegment(new byte[] { nalHeader })); } //Add the depacketized data Depacketized.Add(packetKey, new Common.MemorySegment(packetData, offset, fragment_size)); //Allow If End to Write End Sequence? //Should only be done if last byte is 0? //if (End) Buffer.WriteByte(Media.Codecs.Video.H264.NalUnitType.EndOfSequence); } //No more data? return; } default: //Any other type excluding PayloadContentScalabilityInformation(30) and Reserved(31) { //(Stores the nalUnitType) Write the start code DepacketizeStartCode(ref packetKey, ref nalUnitType, fullStartCodes); //Add the depacketized data Depacketized.Add(packetKey, new Common.MemorySegment(packetData, offset, count - offset)); return; } } }
//Needs to ensure api is not confused with above. could also possibly handle in Packetize by searching for 0 0 1 //public virtual void Packetize(byte[] accessUnit, int mtu = 1500, int? DON = null) //{ // throw new NotImplementedException(); // //Add all data and set marker packet on last packet. // //Add AUD to next packet or the end of this one? //} //Not needed since ProcessPacket can do this. //public void Depacketize(bool ignoreForbiddenZeroBit = true, bool fullStartCodes = false) //{ // //base.Depacketize(); // DisposeBuffer(); // m_Buffer = new MemoryStream(); // var packets = Packets; // //Todo, check if need to // //Order by DON / TSOFFSET (if any packets contains MTAP etc) // //Get all packets in the frame and proces them // foreach (Rtp.RtpPacket packet in packets) // ProcessPacket(packet, ignoreForbiddenZeroBit, fullStartCodes); // //Bring the buffer back the start. (This does not have a weird side effect of adding 0xa to the stream) // m_Buffer.Seek(0, SeekOrigin.Begin); // //This has a weird side effect of adding 0xa to the stream // //m_Buffer.Position = 0; //} /// <summary> /// Depacketizes all contained packets and adds start sequences where necessary which can be though of as a H.264 RBSP /// </summary> /// <param name="packet"></param> public override void Depacketize(Rtp.RtpPacket packet) { ProcessPacket(packet, false, false); }
/// <summary> /// Depacketizes a single packet. /// </summary> /// <param name="packet"></param> /// <param name="containsSps"></param> /// <param name="containsPps"></param> /// <param name="containsSei"></param> /// <param name="containsSlice"></param> /// <param name="isIdr"></param> internal protected virtual void ProcessPacket(Rtp.RtpPacket packet) { //Starting at offset 0 int offset = 0; //Obtain the data of the packet (without source list or padding) byte[] packetData = packet.PayloadData.ToArray(); //Cache the length int count = packetData.Length; //Must have at least 2 bytes if (count <= 2) { return; } //Determine if the forbidden bit is set and the type of nal from the first byte byte firstByte = packetData[offset]; //bool forbiddenZeroBit = ((firstByte & 0x80) >> 7) != 0; byte nalUnitType = (byte)(firstByte & Common.Binary.FiveBitMaxValue); //TODO //o The F bit MUST be cleared if all F bits of the aggregated NAL units are zero; otherwise, it MUST be set. //if (forbiddenZeroBit && nalUnitType <= 23 && nalUnitType > 29) throw new InvalidOperationException("Forbidden Zero Bit is Set."); //Optomize setting out parameters, could be done with a label or with a static function. //Determine what to do switch (nalUnitType) { //Reserved - Ignore case Media.Codecs.Video.H264.NalUnitType.Unknown: case Media.Codecs.Video.H264.NalUnitType.PayloadContentScalabilityInformation: case Media.Codecs.Video.H264.NalUnitType.Reserved: { //May have 4 byte NAL header. //Do not handle return; } case Media.Codecs.Video.H264.NalUnitType.SingleTimeAggregationA: //STAP - A case Media.Codecs.Video.H264.NalUnitType.SingleTimeAggregationB: //STAP - B case Media.Codecs.Video.H264.NalUnitType.MultiTimeAggregation16: //MTAP - 16 case Media.Codecs.Video.H264.NalUnitType.MultiTimeAggregation24: //MTAP - 24 { //Move to Nal Data ++offset; //Todo Determine if need to Order by DON first. //EAT DON for ALL BUT STAP - A if (nalUnitType != Media.Codecs.Video.H264.NalUnitType.SingleTimeAggregationA) { offset += 2; } //Consume the rest of the data from the packet while (offset < count) { //Determine the nal unit size which does not include the nal header int tmp_nal_size = Common.Binary.Read16(packetData, offset, BitConverter.IsLittleEndian); offset += 2; //If the nal had data then write it if (tmp_nal_size > 0) { //Store the nalType contained m_ContainedNalTypes.Add(nalUnitType); //For DOND and TSOFFSET switch (nalUnitType) { case Media.Codecs.Video.H264.NalUnitType.MultiTimeAggregation16: // MTAP - 16 { //SKIP DOND and TSOFFSET offset += 3; goto default; } case Media.Codecs.Video.H264.NalUnitType.MultiTimeAggregation24: // MTAP - 24 { //SKIP DOND and TSOFFSET offset += 4; goto default; } default: { //Read the nal header but don't move the offset byte nalHeader = (byte)(packetData[offset] & Common.Binary.FiveBitMaxValue); //Store the nalType contained m_ContainedNalTypes.Add(nalHeader); if (nalHeader == 6 || nalHeader == 7 || nalHeader == 8) { Buffer.WriteByte(0); } //Done reading break; } } //Write the start code Buffer.Write(Media.Codecs.Video.H264.NalUnitType.StartCode, 0, 3); //Write the nal header and data Buffer.Write(packetData, offset, tmp_nal_size); //Move the offset past the nal offset += tmp_nal_size; } } return; } case Media.Codecs.Video.H264.NalUnitType.FragmentationUnitA: //FU - A case Media.Codecs.Video.H264.NalUnitType.FragmentationUnitB: //FU - B { /* * Informative note: When an FU-A occurs in interleaved mode, it * always follows an FU-B, which sets its DON. * Informative note: If a transmitter wants to encapsulate a single * NAL unit per packet and transmit packets out of their decoding * order, STAP-B packet type can be used. */ //Need 2 bytes if (count > 2) { //Read the Header byte FUHeader = packetData[++offset]; bool Start = ((FUHeader & 0x80) >> 7) > 0; //bool End = ((FUHeader & 0x40) >> 6) > 0; //bool Receiver = (FUHeader & 0x20) != 0; //if (Receiver) throw new InvalidOperationException("Receiver Bit Set"); //Move to data ++offset; //Todo Determine if need to Order by DON first. //DON Present in FU - B if (nalUnitType == 29) { offset += 2; } //Determine the fragment size int fragment_size = count - offset; //If the size was valid if (fragment_size > 0) { //If the start bit was set if (Start) { //Reconstruct the nal header //Use the first 3 bits of the first byte and last 5 bites of the FU Header byte nalHeader = (byte)((firstByte & 0xE0) | (FUHeader & Common.Binary.FiveBitMaxValue)); //Store the nalType contained m_ContainedNalTypes.Add(nalHeader); if (nalHeader == 6 || nalHeader == 7 || nalHeader == 8) { Buffer.WriteByte(0); } //Write the start code Buffer.Write(Media.Codecs.Video.H264.NalUnitType.StartCode, 0, 3); //Write the re-construced header Buffer.WriteByte(nalHeader); } //Allow If End to Write End Sequence? //Write the data of the fragment. Buffer.Write(packetData, offset, fragment_size); } } return; } default: { //Store the nalType contained m_ContainedNalTypes.Add(nalUnitType); if (nalUnitType == 6 || nalUnitType == 7 || nalUnitType == 8) { Buffer.WriteByte(0); } //Write the start code Buffer.Write(Media.Codecs.Video.H264.NalUnitType.StartCode, 0, 3); //Write the nal heaer and data data Buffer.Write(packetData, offset, count - offset); return; } } }
public string ToTextualConvention(FileFormat? format = null) { try { StringBuilder sb = new StringBuilder(); var ts = Timebase.TimeOfDay.Add(TimeSpan.FromMilliseconds(Offset)); if (IsRtcp) sb.Append(RtpSend.ToTextualConvention(format ?? Format, Media.Rtcp.RtcpPacket.GetPackets(Blob, Pointer + sizeOf_RD_packet_T, BlobLength - sizeOf_RD_packet_T), ts, Source)); else using (var rtp = new Rtp.RtpPacket(Blob, Pointer + sizeOf_RD_packet_T)) sb.Append(RtpSend.ToTextualConvention(format ?? Format, rtp, ts, Source)); return sb.ToString(); } catch { throw; } }