/// <summary> /// This method creates as many checksum buffers as exist in the checksum array /// </summary> /// <param name="bytes">Array of buffer chunk that represents the data to encode</param> /// <param name="checksum">Contains 1 BufferChunk, which should be Reset() before calling</param> /// <returns>The checksum packet</returns> public void Encode(BufferChunk[] data, BufferChunk[] checksum) { ValidateObjectNotNull(data); ValidateObjectNotNull(checksum); ValidateNullData(data, 0); ValidateNullData(checksum, 0); ValidateChecksumPacketCount(checksum.Length); // Sort the BufferChunks from longest to shortest ActiveColumns(data, data.Length); // Add 2 bytes so the checksum packet can contain the length of the missing packet int xorDataLength = activeColumns[0].Length + 2; BufferChunk xorData = checksum[0]; int index = xorData.Index; // Store the xor'd lengths of the data xorData += (ushort)(XORLength() ^ xorDataLength); xorData.Reset(index + 2, xorDataLength - 2); // Populate the checksum buffer (xorData) XORData(xorData, xorDataLength - 2); // Set xorData.Index back to 0 xorData.Reset(index, xorDataLength); }
/// <summary> /// Set the payload; this version allows the payload to occupy the space /// reserved for packet padding. This is used by encryption protocols. /// </summary> /// <param name="chunk"></param> internal void SetPaddedPayload(BufferChunk chunk) { if (chunk.Length > (MaxPayloadSize + ReservedPaddingBytes)) { throw new ArgumentOutOfRangeException(string.Format(CultureInfo.CurrentCulture, Strings.ValueMaximumPayload, chunk.Length, MaxPayloadSize)); } // Reset Buffer to just after the header because packets are re-used and so that // operator+ works properly when copying the payload buffer.Reset(0, HeaderSize); buffer += chunk; }
internal virtual void Reset() { buffer.Reset(0, HeaderSize); // Initialize the first byte: V==2, P==0, X==0, CC==0 buffer[VPXCC_INDEX] = (byte)(Rtp.VERSION << 6); }
/// <summary> /// Encode the packets that are in the data BufferChunk array and place the /// encoded result over GF16 in the checksum packets that are in /// the result BufferChunk array. This method encode the size on the first 2 /// bytes of the checksum packet and the data after that. Every checksum BufferChunk /// has the same size, which is the size of the largest BufferChunk in the data BufferChunk /// array, plus one if this BufferChunk doesn't end on a 16 bits boundary, plus /// two to store the encoded length /// </summary> /// <param name="data">The data BufferChunk array (left part of the multiplication)</param> /// <param name="result"></param> /// <param name="encode">The encoding Vandermonde GF16 matrix (right part of the multiplication)</param> public static void EncodeRS(BufferChunk[] data, BufferChunk[] checksum, UInt16[,] encode) { // Get the length in byte of largest BufferChunk in the data BufferChunk array // (which is an array of BufferChunk that could have different sizes) int maxDataLength = GetMaxLength(data); // The checksum packet as a even number of bytes so we can stay with GF16 // and not have to use GF8 for the last byte when length odd int checksumNbRowsByte = maxDataLength; if ((maxDataLength & 1) == 1) { checksumNbRowsByte += 1; } // Add 2 more bytes to store the length checksumNbRowsByte += CFec.SIZE_OVERHEAD; // Encode the length of the data and place that on the first 2 bytes // of the checksum BufferChunk EncodeRSLength(data, checksum, encode); // Start to put encoded data at index + 2, because the 2 first bytes // of the checksum already contain the encoded size for (int i = 0; i < checksum.Length; i++) { BufferChunk bc = checksum[i]; bc.Reset(bc.Index + CFec.SIZE_OVERHEAD, checksumNbRowsByte - CFec.SIZE_OVERHEAD); // Reset all of the checksum packets so they have no data // TODO - Temporary workaround for column based approach - JVE 7/6/2004 bc.Clear(); } // Place all the packets in a 16bits boundary to avoid byte operation // Important: This suppose that the memory reserved for all the bc passed in param // to this method ends on a 16bits boundary (so at least one addition availabe byte // when the last item is on a even address) Queue paddedData = new Queue(); AddPadding(data, 0, paddedData); // Encode the data and place the encoded information in the checksum BufferChunk EncodeRSData(data, checksum, encode, maxDataLength); // Earlier, we changed the boundary of all data BufferChunk // that was not ending on a 16 bits boundary in order to have // better performance during the encoding. Now we need to // restore the data to their original state to be clean. RemovePadding(paddedData); // Restore the BufferChunk index in the checksum checksum BufferChunk // to 0 to be clean. Earlier, We changed the index in order to encode // the data after the encoding length. for (int i = 0; i < checksum.Length; i++) { BufferChunk bc = checksum[i]; bc.Reset(bc.Index - CFec.SIZE_OVERHEAD, checksumNbRowsByte); } }
/// <summary> /// retrieve the next bufferchunk and timestamp for the stream. Return false if there are no more. /// </summary> public bool GetNextFrame(out BufferChunk outFrame, out long timestamp) { outFrame = null; timestamp = 0; bool ret = false; if (streamEndReached) { return(false); } if (currentIndex >= indexCount) { FillBuffer(); } if (currentIndex < indexCount) { int frameLength = 1 + indices[currentIndex].end - indices[currentIndex].start; if (frameLength > 0) { if (indices[currentIndex].timestamp < end) { frame.Reset(indices[currentIndex].start - this.minOffset, frameLength); outFrame = frame; timestamp = indices[currentIndex].timestamp; ret = true; } else { streamEndReached = true; ret = false; } } else { Debug.Fail("Frame of length zero found."); ret = false; } ++currentIndex; return(ret); } else { return(false); } }
/// <summary> /// Decoder: This method takes an array of BufferChunk in parameter with the data /// and checksum received and returns the missing packets. Because it /// is based on XOR, it can recover only from one packet lost. This method /// is very similar than encoder, but differ in the validation part. /// </summary> /// <param name="bytes">Array of buffer chunk that represents the packets where /// you might have lost a packet</param> /// <param name="nbChecksumPackets">Should be always set to 1</param> /// <returns>The missing packet</returns> public void Decode(BufferChunk[] data, int nbChecksumPackets, BufferChunk[] recovery) { ValidateObjectNotNull(data); ValidateObjectNotNull(data[data.Length - 1]); // Checksum packet can't be null ValidateChecksumPacketCount(nbChecksumPackets); ValidateNullData(data, 1); // Sort the BufferChunks from longest to shortest ActiveColumns(data, data.Length - 1); int xorDataLength = XORLength() ^ data[data.Length - 1].NextUInt16(); BufferChunk xorData = recovery[0]; xorData.Reset(xorData.Index, xorDataLength); XORData(xorData, xorDataLength); }
/// <summary> /// Decode to retrieve the missing data packet(s) (null entries) in the data BufferChunk array and place the /// decoded result over GF16 in order in the recovery BufferChunk array with the right length (every /// data packet can have a different length) /// </summary> /// <param name="checksum">The number of checksum packet(s) that were used to encode</param> /// <param name="data">The data BufferChunk array</param> /// <param name="decode">The decoding GF16 matrix</param> /// <param name="recovery">The recovery BufferChunk array to place the recovered result</param> public static void DecodeRS(int checksum, BufferChunk[] data, GF16[,] decode, BufferChunk[] recovery) { // TODO: Validation discussed with Jason: // Check in data array if: the #data lost = # checksum and if this number = recovery.length // Decode the length and set it to the recovery packets DecodeRSLength(data, checksum, decode, recovery); // ... and don't forget to adjust the index of the checksum BufferChunk because // we don't want to take in account the length anymore int dataLength = data.Length; for (int i = dataLength - checksum; i < dataLength; i++) { BufferChunk bc = data[i]; if (bc != null) { bc.Reset(bc.Index + CFec.SIZE_OVERHEAD, bc.Length - CFec.SIZE_OVERHEAD); } } // Adjust all of the data (and recovery) to a 16bit boundary to avoid byte operations // Important: This assumes the provided BufferChunks have room to grow to 16bit boundary Queue paddedBufferChunks = new Queue(); AddPadding(data, checksum, paddedBufferChunks); AddPadding(recovery, 0, paddedBufferChunks); // Decode the data and set the result into the recovery packets DecodeRSData(data, checksum, decode, recovery); // Reset all the packets back to their initial length RemovePadding(paddedBufferChunks); // The caller is responsible for resetting all BufferChunks so there is no need // to reset the checksum packets }
/// <summary> /// This method is called to return buffers to the bufferPool /// </summary> /// <param name="buffer"></param> private void ReturnBuffer(BufferChunk buffer) { buffer.Reset(); bufferPool.Push(buffer); }
/// <summary> /// Send all the frames that should be sent up to this point in time. /// </summary> /// <param name="bytesSent">Number of bytes sent.</param> /// <param name="cumulativeLateness">Sum of temporal disparities over all frames sent.</param> /// <param name="firstStoredTick">The 'start' time on the first index in this whole stream.</param> /// <param name="sender">The RtpSender to send data on.</param> /// <param name="startTimeTicks">The start time of sending, in ticks.</param> /// <param name="timeUntilFrame">The temporal distance between the current frame and the next one to be sent, in ticks.</param> /// <returns>Number of frames sent.</returns> public int SendFrames(RtpSender sender, long timeBoundary, out long timeUntilFrame, ref long totalBytesSent, ref long cumulativeLateness) { if (this.populating) { throw new InvalidOperationException(Strings.BufferplayerSendframesError); } int framesSent = 0; try { while (currentIndex < indexCount && indices[currentIndex].timestamp <= timeBoundary) { long startTimer = DateTime.Now.Ticks; int frameLength = 1 + indices[currentIndex].end - indices[currentIndex].start; if (frameLength > 0) { // Calculate how late the frame will be long lateness = (timeBoundary - indices[currentIndex].timestamp) / Constants.TicksPerMs; if (lateness > Constants.TicksSpent) { Trace.WriteLine(String.Format(CultureInfo.InvariantCulture, "--- FRAME LATENESS OF: {0} ms", lateness)); } cumulativeLateness += lateness; // Send Frame buffer.Reset(indices[currentIndex].start - indices[0].start, frameLength); sender.Send(buffer); } else { // (pbristow) Why would this happen??? Debug.Fail("Frame of length zero found."); } totalBytesSent += frameLength; ++framesSent; ++currentIndex; long takenTime = DateTime.Now.Ticks - startTimer; if (takenTime > Constants.TicksSpent) { Trace.WriteLine(String.Format(CultureInfo.InvariantCulture, "TIME WASTED TO SEND A FRAME: {0} ms, ID: {1}, bytes: {2}", (takenTime / Constants.TicksPerMs), streamID, frameLength)); } } if (currentIndex == indexCount) { timeUntilFrame = 0; } else { timeUntilFrame = (indices[currentIndex].timestamp - timeBoundary); } } catch (ObjectDisposedException) { // Sender is disposed by Stop being called timeUntilFrame = long.MaxValue; } return(framesSent); }
/// <summary> /// Decode to retrieve the length of the missing data packet(s) (null entries) in the /// data BufferChunk array and set the length of the packets in the recovery BufferChunk array. /// We have to do that because every data packet could have a different length. /// </summary> /// <param name="data">The data BufferChunk array</param> /// <param name="checksum">The number of checksum packet(s) that were used to encode</param> /// <param name="decode">The decoding GF16 matrix</param> /// <param name="recovery">The recovery BufferChunk array to set the length of the recovered packets</param> private static void DecodeRSLength(BufferChunk[] data, int checksum, GF16[,] decode, BufferChunk[] recovery) { // Important! For now I assume the following in the firstMatrix: // - The array is always of size data + checksum // - #checksum not null entries = #packet lost // - recovery.length is exactly the number of data packet lost // - checksum param is # checksum packets that were used to encode // Get the length once to avoid having the overhead of getting it again inside a inner (performance) // Note that this value is also used outside of this method, so we could have had a parameter too, // but I prefer to get it here to avoid inconsitencies int dataLength = data.Length; int recoveryColumn = 0; for (int dataColumn = 0; dataColumn < dataLength - checksum; dataColumn++) { // recover only the missing column if (data[dataColumn] == null) { // Inverted matrix row index int imRowIndex = 0; // length of the recovered buffer UInt16 currentLength = 0; for (int dataIndex = 0; dataIndex < dataLength; dataIndex++) { // Perform the core operation mult with add in GF16 // TODO: We could crunch the column so we avoid this test if (data[dataIndex] != null) { UInt16 length = 0; if (dataIndex < dataLength - checksum) // For the data part, we get the length of the BufferChunk { length = (UInt16)data[dataIndex].Length; } else // For the checksum part, the encoded length is inside the first 2 bytes { length = data[dataIndex].GetUInt16(0); } UInt16 currentValue = GF16.Multiply(length, decode[dataColumn, imRowIndex].Value); currentLength = GF16.Add(currentLength, currentValue); imRowIndex++; } // if } // for column (do elementary operations) BufferChunk bc = recovery[recoveryColumn]; bc.Reset(bc.Index, currentLength); // Reset all of the recovery packets so they have no data // TODO - Temporary workaround for column based approach - JVE 7/6/2004 bc.Clear(); recoveryColumn++; } // if } // for dataColumn }
override public void Run() { BufferChunk bc = new BufferChunk(7); short s = 25000; int i = 545434; byte b = 56; bc += s; bc += i; bc += b; if (bc.NextInt16() != s) throw new TestCaseException("Short failed for " + s); if (bc.NextInt32() != i) throw new TestCaseException("Int failed for " + i); if (bc.NextByte() != b) throw new TestCaseException("Byte failed for " + b); bc.Reset(); s = short.MaxValue; i = int.MaxValue; b = byte.MaxValue; bc += s; bc += i; bc += b; if (bc.NextInt16() != s) throw new TestCaseException("Short failed for " + s); if (bc.NextInt32() != i) throw new TestCaseException("Int failed for " + i); if (bc.NextByte() != b) throw new TestCaseException("Byte failed for " + b); bc.Reset(); s = short.MinValue; i = int.MinValue; b = byte.MinValue; bc += s; bc += i; bc += b; if (bc.NextInt16() != s) throw new TestCaseException("Short failed for " + s); if (bc.NextInt32() != i) throw new TestCaseException("Int failed for " + i); if (bc.NextByte() != b) throw new TestCaseException("Byte failed for " + b); }
/// <summary> /// This method is called to return buffers to the bufferPool /// </summary> /// <param name="buffer"></param> private void ReturnBuffer(BufferChunk buffer) { buffer.Reset(); if (null != bufferPool) { bufferPool.Push(buffer); } else { Console.WriteLine("RtpListener.ReturnBuffer() - Buffer Pool is null"); } }
/// <summary> /// Called by RtcpSender when it is time to collect Rtcp data /// </summary> /// <returns>A CompoundPacketBuilder</returns> CompoundPacketBuilder RtcpSender.IRtpSession.RtcpReportIntervalReached() { // Limit the frequency of the cleanup in order to avoid participants and streams being // inappropriately designated as stale. This can happen if we start up a lot of senders at once. // Each sender triggers an RTCP packet which without the frequency limitation could // increment the stale counter to its limit and cause the participant or stream to be removed. bool cleanUpNow = false; if (DateTime.Now > this.lastCleanUp.AddSeconds(5)) { cleanUpNow = true; this.lastCleanUp = DateTime.Now; } // A stale participant is one who is not sending Rtcp data if (cleanUpNow) CheckForStaleParticipants(); // Add participant data Debug.Assert(participant.SSRC != 0); cpb.ParticipantData(participant); // Add Rtp data if(rtpTraffic) { // A stale stream is one not sending Rtp traffic if (cleanUpNow) CheckForStaleStreams(); // Collect SenderReportPackets and SDESReports from each Sender lock(rtpSenders) { foreach(RtpSender sender in rtpSenders.Values) { // this adds the SR and SDES packets sender.UpdateRtcpData(); } } // add a sdes (source description) //cpb.Add_SDESReport(participant.SSRC, participant); // Collect ReceiverReports from each of the streams lock(streamsAndIPs) { foreach(IPStreamPairHashtable.IPStreamPair ipsp in streamsAndIPs.Values) { if( ipsp.stream != null ) { ipsp.stream.AddReceiverReport(cpb); } } } } #if USE_APP_PACKET // andrew: Add an app packet that reflects the time and the current venue... if ((VenueName != null) && (this.groupEP != null)) { string appPayload = VenueName + "#" + this.groupEP.Address.ToString(); long time = DateTime.Now.Ticks; // 8 bytes for time + venue name int count = 8 + utf8.GetByteCount(appPayload); // count must be 4-byte alligned int mod = count % 4; if (mod != 0) { count += (4 - mod); } BufferChunk buffer = new BufferChunk(count); buffer.Reset(0, count); buffer.Clear(); buffer.SetInt64(0, time); buffer.SetUTF8String(8, appPayload); byte[] rawBytes = buffer.Buffer; cpb.Add_APPReport(participant.SSRC, Rtcp.APP_PACKET_NAME, Rtcp.VENUE_APP_PACKET_SUBTYPE, rawBytes); } #endif return cpb; }
override public void Run() { BufferChunk bc = new BufferChunk(7); short s = 25000; int i = 545434; byte b = 56; bc += s; bc += i; bc += b; if (bc.NextInt16() != s) { throw new TestCaseException("Short failed for " + s); } if (bc.NextInt32() != i) { throw new TestCaseException("Int failed for " + i); } if (bc.NextByte() != b) { throw new TestCaseException("Byte failed for " + b); } bc.Reset(); s = short.MaxValue; i = int.MaxValue; b = byte.MaxValue; bc += s; bc += i; bc += b; if (bc.NextInt16() != s) { throw new TestCaseException("Short failed for " + s); } if (bc.NextInt32() != i) { throw new TestCaseException("Int failed for " + i); } if (bc.NextByte() != b) { throw new TestCaseException("Byte failed for " + b); } bc.Reset(); s = short.MinValue; i = int.MinValue; b = byte.MinValue; bc += s; bc += i; bc += b; if (bc.NextInt16() != s) { throw new TestCaseException("Short failed for " + s); } if (bc.NextInt32() != i) { throw new TestCaseException("Int failed for " + i); } if (bc.NextByte() != b) { throw new TestCaseException("Byte failed for " + b); } }