private void Send() { try { int payloadSize = RTPPacketSendSize; RTPPacket rtpPacket = new RTPPacket(RTPPacketSendSize); byte[] rtpBytes = rtpPacket.GetBytes(); RTPHeader rtpHeader = new RTPHeader(); rtpHeader.SequenceNumber = (UInt16)65000; //Convert.ToUInt16(Crypto.GetRandomInt(0, UInt16.MaxValue)); uint sendTimestamp = uint.MaxValue - 5000; uint lastSendTimestamp = sendTimestamp; UInt16 lastSeqNum = 0; logger.Debug("RTP send stream starting to " + IPSocket.GetSocketString(m_streamEndPoint) + " with payload size " + payloadSize + " bytes."); Sending = true; m_startRTPSendTime = DateTime.MinValue; m_lastRTPSentTime = DateTime.MinValue; m_sampleStartSeqNo = rtpHeader.SequenceNumber; DateTime lastRTPSendAttempt = DateTime.Now; while (m_udpListener != null && !StopListening) { // This may be changed by the listener so it needs to be set each iteration. IPEndPoint dstEndPoint = m_streamEndPoint; //logger.Info("Sending RTP packet to " + dstEndPoint.Address + ":" + dstEndPoint.Port); if (payloadSize != m_rtpPacketSendSize) { payloadSize = m_rtpPacketSendSize; logger.Info("Changing RTP payload to " + payloadSize); rtpPacket = new RTPPacket(RTP_HEADER_SIZE + m_rtpPacketSendSize); rtpBytes = rtpPacket.GetBytes(); } try { if (m_startRTPSendTime == DateTime.MinValue) { m_startRTPSendTime = DateTime.Now; rtpHeader.MarkerBit = 0; logger.Debug("RTPSink Send SyncSource=" + rtpPacket.Header.SyncSource + "."); } else { lastSendTimestamp = sendTimestamp; double milliSinceLastSend = DateTime.Now.Subtract(m_lastRTPSentTime).TotalMilliseconds; sendTimestamp = Convert.ToUInt32((lastSendTimestamp + (milliSinceLastSend * TIMESTAMP_FACTOR)) % uint.MaxValue); if (lastSendTimestamp > sendTimestamp) { logger.Error("RTP Sender previous timestamp (" + lastSendTimestamp + ") > timestamp (" + sendTimestamp + ") ms since last send=" + milliSinceLastSend + ", lastseqnum=" + lastSeqNum + ", seqnum=" + rtpHeader.SequenceNumber + "."); } if (DateTime.Now.Subtract(m_lastRTPSentTime).TotalMilliseconds > 75) { logger.Debug("delayed send: " + rtpHeader.SequenceNumber + ", time since last send " + DateTime.Now.Subtract(m_lastRTPSentTime).TotalMilliseconds + "ms."); } } rtpHeader.Timestamp = sendTimestamp; byte[] rtpHeaderBytes = rtpHeader.GetBytes(); Array.Copy(rtpHeaderBytes, 0, rtpBytes, 0, rtpHeaderBytes.Length); // Send RTP packets and any extra channels required to emulate mutliple calls. for (int channelCount = 0; channelCount < m_channels; channelCount++) { //logger.Debug("Send rtp getting wallclock timestamp for " + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss:fff")); //DateTime sendTime = DateTime.Now; //rtpHeader.Timestamp = RTPHeader.GetWallclockUTCStamp(sendTime); //logger.Debug(rtpHeader.SequenceNumber + "," + rtpHeader.Timestamp); m_udpListener.Send(rtpBytes, rtpBytes.Length, dstEndPoint); m_lastRTPSentTime = DateTime.Now; m_packetsSent++; m_bytesSent += rtpBytes.Length; if (m_packetsSent % 500 == 0) { logger.Debug("Total packets sent to " + dstEndPoint.ToString() + " " + m_packetsSent + ", bytes " + NumberFormatter.ToSIByteFormat(m_bytesSent, 2) + "."); } //sendLogger.Info(m_lastRTPSentTime.ToString("dd MMM yyyy HH:mm:ss:fff") + "," + m_lastRTPSentTime.Subtract(m_startRTPSendTime).TotalMilliseconds.ToString("0") + "," + rtpHeader.SequenceNumber + "," + rtpBytes.Length); //sendLogger.Info(rtpHeader.SequenceNumber + "," + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss:fff")); if (DataSent != null) { try { DataSent(m_streamId, rtpBytes, dstEndPoint); } catch (Exception excp) { logger.Error("Exception RTPSink DataSent. " + excp.Message); } } lastSeqNum = rtpHeader.SequenceNumber; if (rtpHeader.SequenceNumber == UInt16.MaxValue) { //logger.Debug("RTPSink looping the sequence number in sample."); rtpHeader.SequenceNumber = 0; } else { rtpHeader.SequenceNumber++; } } } catch (Exception excp) { logger.Error("Exception RTP Send. " + excp.GetType() + ". " + excp.Message); if (excp.GetType() == typeof(SocketException)) { logger.Error("socket exception errorcode=" + ((SocketException)excp).ErrorCode + "."); } logger.Warn("Remote socket closed on send. Last RTP recevied " + m_lastRTPReceivedTime.ToString("dd MMM yyyy HH:mm:ss") + ", last RTP successfull send " + m_lastRTPSentTime.ToString("dd MMM yyyy HH:mm:ss") + "."); } Thread.Sleep(RTPFrameSize); #region Check for whether the stream has timed out on a send or receive and if so shut down the stream. double noRTPRcvdDuration = (m_lastRTPReceivedTime != DateTime.MinValue) ? DateTime.Now.Subtract(m_lastRTPReceivedTime).TotalSeconds : 0; double noRTPSentDuration = (m_lastRTPSentTime != DateTime.MinValue) ? DateTime.Now.Subtract(m_lastRTPSentTime).TotalSeconds : 0; double testDuration = DateTime.Now.Subtract(m_startRTPSendTime).TotalSeconds; if (( noRTPRcvdDuration > NO_RTP_TIMEOUT || noRTPSentDuration > NO_RTP_TIMEOUT || (m_lastRTPReceivedTime == DateTime.MinValue && testDuration > NO_RTP_TIMEOUT) ) && // If the test request comes from a private or unreachable IP address then no RTP will ever be received. StopIfNoData) { logger.Warn("Disconnecting RTP stream on " + m_localEndPoint.Address.ToString() + ":" + m_localEndPoint.Port + " due to not being able to send any RTP for " + NO_RTP_TIMEOUT + "s."); StopListening = true; } // Shutdown the socket even if there is still RTP but the stay alive limit has been exceeded. if (RTPMaxStayAlive > 0 && DateTime.Now.Subtract(m_startRTPSendTime).TotalSeconds > RTPMaxStayAlive) { logger.Warn("Shutting down RTPSink due to passing RTPMaxStayAlive time."); Shutdown(); StopListening = true; } #endregion } } catch (Exception excp) { logger.Error("Exception Send RTPSink: " + excp.Message); } finally { #region Shut down socket. Shutdown(); if (SenderClosed != null) { try { SenderClosed(m_streamId, m_callDescriptorId); } catch (Exception excp) { logger.Error("Exception RTPSink SenderClosed. " + excp.Message); } } #endregion } }
private void Listen() { try { UdpClient udpSvr = m_udpListener; if (udpSvr == null) { logger.Error( "The UDP server was not correctly initialised in the RTP sink when attempting to start the listener, the RTP stream has not been intialised."); return; } else { logger.Debug("RTP Listener now listening on " + m_localEndPoint.Address + ":" + m_localEndPoint.Port + "."); } IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); byte[] rcvdBytes = null; m_startRTPReceiveTime = DateTime.MinValue; m_lastRTPReceivedTime = DateTime.MinValue; DateTime previousRTPReceiveTime = DateTime.MinValue; uint previousTimestamp = 0; ushort sequenceNumber = 0; ushort previousSeqNum = 0; uint senderSendSpacing = 0; uint lastSenderSendSpacing = 0; while (!StopListening) { rcvdBytes = null; try { rcvdBytes = udpSvr.Receive(ref remoteEndPoint); } catch { //logger.Warn("Remote socket closed on receive. Last RTP received " + m_lastRTPReceivedTime.ToString("dd MMM yyyy HH:mm:ss") + ", last RTP successfull send " + m_lastRTPSentTime.ToString("dd MMM yyyy HH:mm:ss") + "."); } if (rcvdBytes != null && rcvdBytes.Length > 0) { // Check whether this is an RTCP report. UInt16 firstWord = BitConverter.ToUInt16(rcvdBytes, 0); if (BitConverter.IsLittleEndian) { firstWord = NetConvert.DoReverseEndian(firstWord); } ushort packetType = 0; if (BitConverter.IsLittleEndian) { packetType = Convert.ToUInt16(firstWord & 0x00ff); } if (packetType == RTCPHeader.RTCP_PACKET_TYPE) { logger.Debug("RTP Listener received remote RTCP report from " + remoteEndPoint + "."); try { RTCPPacket rtcpPacket = new RTCPPacket(rcvdBytes); RTCPReportPacket rtcpReportPacket = new RTCPReportPacket(rtcpPacket.Reports); if (RTCPReportReceived != null) { RTCPReportReceived(this, rtcpReportPacket); } } catch (Exception rtcpExcp) { logger.Error("Exception processing remote RTCP report. " + rtcpExcp.Message); } continue; } // Channel statistics. DateTime rtpReceiveTime = DateTime.Now; if (m_startRTPReceiveTime == DateTime.MinValue) { m_startRTPReceiveTime = rtpReceiveTime; //m_sampleStartTime = rtpReceiveTime; } previousRTPReceiveTime = new DateTime(m_lastRTPReceivedTime.Ticks); m_lastRTPReceivedTime = rtpReceiveTime; m_packetsReceived++; m_bytesReceived += rcvdBytes.Length; previousSeqNum = sequenceNumber; // This stops the thread running the ListenerTimeout method from deciding the strema has recieved no RTP and therefore should be shutdown. m_lastPacketReceived.Set(); // Let whoever has subscribed that an RTP packet has been received. if (DataReceived != null) { try { DataReceived(m_streamId, rcvdBytes, remoteEndPoint); } catch (Exception excp) { logger.Error("Exception RTPSink DataReceived. " + excp.Message); } } if (m_packetsReceived % 500 == 0) { logger.Debug("Total packets received from " + remoteEndPoint.ToString() + " " + m_packetsReceived + ", bytes " + NumberFormatter.ToSIByteFormat(m_bytesReceived, 2) + "."); } try { RTPPacket rtpPacket = new RTPPacket(rcvdBytes); uint syncSource = rtpPacket.Header.SyncSource; uint timestamp = rtpPacket.Header.Timestamp; sequenceNumber = rtpPacket.Header.SequenceNumber; //logger.Debug("seqno=" + rtpPacket.Header.SequenceNumber + ", timestamp=" + timestamp); if (previousRTPReceiveTime != DateTime.MinValue) { //uint senderSendSpacing = rtpPacket.Header.Timestamp - previousTimestamp; // Need to cope with cases where the timestamp has looped, if this timestamp is < last timesatmp and there is a large difference in them then it's because the timestamp counter has looped. lastSenderSendSpacing = senderSendSpacing; senderSendSpacing = (Math.Abs(timestamp - previousTimestamp) > (uint.MaxValue / 2)) ? timestamp + uint.MaxValue - previousTimestamp : timestamp - previousTimestamp; if (previousTimestamp > timestamp) { logger.Error("BUG: Listener previous timestamp (" + previousTimestamp + ") > timestamp (" + timestamp + "), last seq num=" + previousSeqNum + ", seqnum=" + sequenceNumber + "."); // Cover for this bug until it's nailed down. senderSendSpacing = lastSenderSendSpacing; } double senderSpacingMilliseconds = (double)senderSendSpacing / (double)TIMESTAMP_FACTOR; double interarrivalReceiveTime = m_lastRTPReceivedTime.Subtract(previousRTPReceiveTime) .TotalMilliseconds; #region RTCP reporting. if (m_rtcpSampler == null) { //resultsLogger.Info("First Packet: " + rtpPacket.Header.SequenceNumber + "," + m_arrivalTime.ToString("HH:mm:fff")); m_rtcpSampler = new RTCPReportSampler(m_streamId, syncSource, remoteEndPoint, rtpPacket.Header.SequenceNumber, m_lastRTPReceivedTime, rcvdBytes.Length); m_rtcpSampler.RTCPReportReady += new RTCPSampleReadyDelegate(m_rtcpSampler_RTCPReportReady); m_rtcpSampler.StartSampling(); } else { //m_receiverReports[syncSource].RecordRTPReceive(rtpPacket.Header.SequenceNumber, sendTime, rtpReceiveTime, rcvdBytes.Length); // Transit time is calculated by knowing that the sender sent a packet at a certain time after the last send and the receiver received a pakcet a certain time after the last receive. // The difference in these two times is the jitter present. The transit time can change with each transimission and as this methid relies on two sends two packet // arrivals to calculate the transit time it's not going to be perfect (you'd need synchronised NTP clocks at each end to be able to be accurate). // However if used tor an average calculation it should be pretty close. //double transitTime = Math.Abs(interarrivalReceiveTime - senderSpacingMilliseconds); uint jitter = (interarrivalReceiveTime - senderSpacingMilliseconds > 0) ? Convert.ToUInt32(interarrivalReceiveTime - senderSpacingMilliseconds) : 0; if (jitter > 75) { logger.Debug("seqno=" + rtpPacket.Header.SequenceNumber + ", timestmap=" + timestamp + ", ts-prev=" + previousTimestamp + ", receive spacing=" + interarrivalReceiveTime + ", send spacing=" + senderSpacingMilliseconds + ", jitter=" + jitter); } else { //logger.Debug("seqno=" + rtpPacket.Header.SequenceNumber + ", receive spacing=" + interarrivalReceiveTime + ", timestamp=" + timestamp + ", transit time=" + transitTime); } m_rtcpSampler.RecordRTPReceive(m_lastRTPReceivedTime, rtpPacket.Header.SequenceNumber, rcvdBytes.Length, jitter); } #endregion } else { logger.Debug("RTPSink Listen SyncSource=" + rtpPacket.Header.SyncSource + "."); } previousTimestamp = timestamp; } catch (Exception excp) { logger.Error("Received data was not a valid RTP packet. " + excp.Message); } #region Switching endpoint if required to cope with NAT. // If a packet is recieved from an endpoint that wasn't expected treat the stream as being NATted and switch the endpoint to the socket on the NAT server. try { if (m_streamEndPoint != null && m_streamEndPoint.Address != null && remoteEndPoint != null && remoteEndPoint.Address != null && (m_streamEndPoint.Address.ToString() != remoteEndPoint.Address.ToString() || m_streamEndPoint.Port != remoteEndPoint.Port)) { logger.Debug("Expecting RTP on " + IPSocket.GetSocketString(m_streamEndPoint) + " but received on " + IPSocket.GetSocketString(remoteEndPoint) + ", now sending to " + IPSocket.GetSocketString(remoteEndPoint) + "."); m_streamEndPoint = remoteEndPoint; if (RemoteEndPointChanged != null) { try { RemoteEndPointChanged(m_streamId, remoteEndPoint); } catch (Exception changeExcp) { logger.Error("Exception RTPListener Changing Remote EndPoint. " + changeExcp.Message); } } } } catch (Exception setSendExcp) { logger.Error("Exception RTPListener setting SendTo Socket. " + setSendExcp.Message); } #endregion } else if (!StopListening ) // Empty packet was received possibly indicating connection closure so check for timeout. { double noRTPRcvdDuration = (m_lastRTPReceivedTime != DateTime.MinValue) ? DateTime.Now.Subtract(m_lastRTPReceivedTime).TotalSeconds : 0; double noRTPSentDuration = (m_lastRTPSentTime != DateTime.MinValue) ? DateTime.Now.Subtract(m_lastRTPSentTime).TotalSeconds : 0; //logger.Warn("Remote socket closed on receive on " + m_localEndPoint.Address.ToString() + ":" + + m_localEndPoint.Port + ", reinitialising. No rtp for " + noRTPRcvdDuration + "s. last rtp " + m_lastRTPReceivedTime.ToString("dd MMM yyyy HH:mm:ss") + "."); // If this check is not done then the stream will never time out if it doesn't receive the first packet. if (m_lastRTPReceivedTime == DateTime.MinValue) { m_lastRTPReceivedTime = DateTime.Now; } remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); if ((noRTPRcvdDuration > NO_RTP_TIMEOUT || noRTPSentDuration > NO_RTP_TIMEOUT) && StopIfNoData) { logger.Warn("Disconnecting RTP listener on " + m_localEndPoint.ToString() + " due to not being able to send or receive any RTP for " + NO_RTP_TIMEOUT + "s."); Shutdown(); } } } } catch (Exception excp) { logger.Error("Exception Listen RTPSink: " + excp.Message); } finally { #region Shut down socket. Shutdown(); if (ListenerClosed != null) { try { ListenerClosed(m_streamId, m_callDescriptorId); } catch (Exception excp) { logger.Error("Exception RTPSink ListenerClosed. " + excp.Message); } } #endregion } }
/// <summary> /// Sends a dynamically sized frame. The RTP marker bit will be set for the last transmitted packet in the frame. /// </summary> /// <param name="frame">The frame to transmit.</param> /// <param name="frameSpacing">The increment to add to the RTP timestamp for each new frame.</param> /// <param name="payloadType">The payload type to set on the RTP packet.</param> public void SendVP8Frame(byte[] frame, uint frameSpacing, int payloadType) { try { if (!IsRunning) { logger.Warn("SendVP8Frame cannot be called on a closed RTP channel."); } else if (_rtpSocketError != SocketError.Success) { logger.Warn("SendVP8Frame was called for an RTP socket in an error state of " + _rtpSocketError + "."); } else { _timestamp = (_timestamp == 0) ? DateTimeToNptTimestamp32(DateTime.Now) : (_timestamp + frameSpacing) % UInt32.MaxValue; //System.Diagnostics.Debug.WriteLine("Sending " + frame.Length + " encoded bytes to client, timestamp " + _timestamp + ", starting sequence number " + _sequenceNumber + "."); for (int index = 0; index *RTP_MAX_PAYLOAD < frame.Length; index++) { //byte[] vp8HeaderBytes = (index == 0) ? new byte[VP8_RTP_HEADER_LENGTH] { 0x90, 0x80, (byte)(_sequenceNumber % 128) } : new byte[VP8_RTP_HEADER_LENGTH] { 0x80, 0x80, (byte)(_sequenceNumber % 128) }; byte[] vp8HeaderBytes = (index == 0) ? new byte[VP8_RTP_HEADER_LENGTH] { 0x10 } : new byte[VP8_RTP_HEADER_LENGTH] { 0x00 }; int offset = index * RTP_MAX_PAYLOAD; int payloadLength = ((index + 1) * RTP_MAX_PAYLOAD < frame.Length) ? RTP_MAX_PAYLOAD : frame.Length - index * RTP_MAX_PAYLOAD; // RTPPacket rtpPacket = new RTPPacket(payloadLength + VP8_RTP_HEADER_LENGTH + ((_srtp != null) ? SRTP_SIGNATURE_LENGTH : 0)); RTPPacket rtpPacket = new RTPPacket(payloadLength + VP8_RTP_HEADER_LENGTH); rtpPacket.Header.SyncSource = _syncSource; rtpPacket.Header.SequenceNumber = _sequenceNumber++; rtpPacket.Header.Timestamp = _timestamp; rtpPacket.Header.MarkerBit = (offset + payloadLength >= frame.Length) ? 1 : 0; rtpPacket.Header.PayloadType = payloadType; Buffer.BlockCopy(vp8HeaderBytes, 0, rtpPacket.Payload, 0, vp8HeaderBytes.Length); Buffer.BlockCopy(frame, offset, rtpPacket.Payload, vp8HeaderBytes.Length, payloadLength); byte[] rtpBytes = rtpPacket.GetBytes(); //if (_srtp != null) //{ // int rtperr = _srtp.ProtectRTP(rtpBytes, rtpBytes.Length - SRTP_SIGNATURE_LENGTH); // if (rtperr != 0) // { // logger.Warn("An error was returned attempting to sign an SRTP packet for " + _remoteEndPoint + ", error code " + rtperr + "."); // } //} //System.Diagnostics.Debug.WriteLine(" offset " + (index * RTP_MAX_PAYLOAD) + ", payload length " + payloadLength + ", sequence number " + rtpPacket.Header.SequenceNumber + ", marker " + rtpPacket.Header .MarkerBit + "."); //Stopwatch sw = new Stopwatch(); //sw.Start(); _rtpSocket.SendTo(rtpBytes, rtpBytes.Length, SocketFlags.None, _remoteEP); //sw.Stop(); //if (sw.ElapsedMilliseconds > 15) //{ // logger.Warn(" SendVP8Frame offset " + offset + ", payload length " + payloadLength + ", sequence number " + rtpPacket.Header.SequenceNumber + ", marker " + rtpPacket.Header.MarkerBit + ", took " + sw.ElapsedMilliseconds + "ms."); //} } } } catch (Exception excp) { if (IsRunning) { logger.Warn("Exception RTPChannel.SendVP8Frame attempting to send to the RTP socket at " + _remoteEP + ". " + excp); if (OnRTPSocketDisconnected != null) { OnRTPSocketDisconnected(); } } } }
/// <summary> /// H264 frames need a two byte header when transmitted over RTP. /// </summary> /// <param name="frame">The H264 encoded frame to transmit.</param> /// <param name="frameSpacing">The increment to add to the RTP timestamp for each new frame.</param> /// <param name="payloadType">The payload type to set on the RTP packet.</param> public void SendH264Frame(byte[] frame, uint frameSpacing, int payloadType) { try { if (!IsRunning) { logger.Warn("SendH264Frame cannot be called on a closed session."); } else if (_rtpSocketError != SocketError.Success) { logger.Warn("SendH264Frame was called for an RTP socket in an error state of " + _rtpSocketError + "."); } else { _timestamp = (_timestamp == 0) ? DateTimeToNptTimestamp32(DateTime.Now) : (_timestamp + frameSpacing) % UInt32.MaxValue; //System.Diagnostics.Debug.WriteLine("Sending " + frame.Length + " H264 encoded bytes to client, timestamp " + _timestamp + ", starting sequence number " + _sequenceNumber + "."); for (int index = 0; index *RTP_MAX_PAYLOAD < frame.Length; index++) { uint offset = Convert.ToUInt32(index * RTP_MAX_PAYLOAD); int payloadLength = ((index + 1) * RTP_MAX_PAYLOAD < frame.Length) ? RTP_MAX_PAYLOAD : frame.Length - index * RTP_MAX_PAYLOAD; RTPPacket rtpPacket = new RTPPacket(payloadLength + H264_RTP_HEADER_LENGTH); rtpPacket.Header.SyncSource = _syncSource; rtpPacket.Header.SequenceNumber = _sequenceNumber++; rtpPacket.Header.Timestamp = _timestamp; rtpPacket.Header.MarkerBit = 0; rtpPacket.Header.PayloadType = payloadType; // Start RTP packet in frame 0x1c 0x89 // Middle RTP packet in frame 0x1c 0x09 // Last RTP packet in frame 0x1c 0x49 byte[] h264Header = new byte[] { 0x1c, 0x09 }; if (index == 0 && frame.Length < RTP_MAX_PAYLOAD) { // First and last RTP packet in the frame. h264Header = new byte[] { 0x1c, 0x49 }; rtpPacket.Header.MarkerBit = 1; } else if (index == 0) { h264Header = new byte[] { 0x1c, 0x89 }; } else if ((index + 1) * RTP_MAX_PAYLOAD > frame.Length) { h264Header = new byte[] { 0x1c, 0x49 }; rtpPacket.Header.MarkerBit = 1; } var h264Stream = frame.Skip(index * RTP_MAX_PAYLOAD).Take(payloadLength).ToList(); h264Stream.InsertRange(0, h264Header); rtpPacket.Payload = h264Stream.ToArray(); byte[] rtpBytes = rtpPacket.GetBytes(); //System.Diagnostics.Debug.WriteLine(" offset " + (index * RTP_MAX_PAYLOAD) + ", payload length " + payloadLength + ", sequence number " + rtpPacket.Header.SequenceNumber + ", marker " + rtpPacket.Header .MarkerBit + "."); //Stopwatch sw = new Stopwatch(); //sw.Start(); _rtpSocket.SendTo(rtpBytes, rtpBytes.Length, SocketFlags.None, _remoteEP); //sw.Stop(); //if (sw.ElapsedMilliseconds > 15) //{ // logger.Warn(" SendH264Frame offset " + offset + ", payload length " + payloadLength + ", sequence number " + rtpPacket.Header.SequenceNumber + ", marker " + rtpPacket.Header.MarkerBit + ", took " + sw.ElapsedMilliseconds + "ms."); //} } } } catch (Exception excp) { if (IsRunning) { logger.Warn("Exception RTPChannel.SendH264Frame attempting to send to the RTP socket at " + _remoteEP + ". " + excp); if (OnRTPSocketDisconnected != null) { OnRTPSocketDisconnected(); } } } }
/// <summary> /// Helper method to send a low quality JPEG image over RTP. This method supports a very abbreviated version of RFC 2435 "RTP Payload Format for JPEG-compressed Video". /// It's intended as a quick convenient way to send something like a test pattern image over an RTSP connection. More than likely it won't be suitable when a high /// quality image is required since the header used in this method does not support quantization tables. /// </summary> /// <param name="jpegBytes">The raw encoded bytes of teh JPEG image to transmit.</param> /// <param name="jpegQuality">The encoder quality of the JPEG image.</param> /// <param name="jpegWidth">The width of the JPEG image.</param> /// <param name="jpegHeight">The height of the JPEG image.</param> /// <param name="framesPerSecond">The rate at which the JPEG frames are being transmitted at. used to calculate the timestamp.</param> public void SendJpegFrame(byte[] jpegBytes, int jpegQuality, int jpegWidth, int jpegHeight, int framesPerSecond) { try { if (!IsRunning) { logger.Warn("SendJpegFrame cannot be called on a closed session."); } else if (_rtpSocketError != SocketError.Success) { logger.Warn("SendJpegFrame was called for an RTP socket in an error state of " + _rtpSocketError + "."); } else { _timestamp = (_timestamp == 0) ? DateTimeToNptTimestamp32(DateTime.Now) : (_timestamp + (uint)(RFC_2435_FREQUENCY_BASELINE / framesPerSecond)) % UInt32.MaxValue; //System.Diagnostics.Debug.WriteLine("Sending " + jpegBytes.Length + " encoded bytes to client, timestamp " + _timestamp + ", starting sequence number " + _sequenceNumber + ", image dimensions " + jpegWidth + " x " + jpegHeight + "."); for (int index = 0; index *RTP_MAX_PAYLOAD < jpegBytes.Length; index++) { uint offset = Convert.ToUInt32(index * RTP_MAX_PAYLOAD); int payloadLength = ((index + 1) * RTP_MAX_PAYLOAD < jpegBytes.Length) ? RTP_MAX_PAYLOAD : jpegBytes.Length - index * RTP_MAX_PAYLOAD; byte[] jpegHeader = CreateLowQualityRtpJpegHeader(offset, jpegQuality, jpegWidth, jpegHeight); List <byte> packetPayload = new List <byte>(); packetPayload.AddRange(jpegHeader); packetPayload.AddRange(jpegBytes.Skip(index * RTP_MAX_PAYLOAD).Take(payloadLength)); RTPPacket rtpPacket = new RTPPacket(packetPayload.Count); rtpPacket.Header.SyncSource = _syncSource; rtpPacket.Header.SequenceNumber = _sequenceNumber++; rtpPacket.Header.Timestamp = _timestamp; rtpPacket.Header.MarkerBit = ((index + 1) * RTP_MAX_PAYLOAD < jpegBytes.Length) ? 0 : 1; rtpPacket.Header.PayloadType = (int)SDPMediaFormatsEnum.JPEG; rtpPacket.Payload = packetPayload.ToArray(); byte[] rtpBytes = rtpPacket.GetBytes(); //System.Diagnostics.Debug.WriteLine(" offset " + offset + ", payload length " + payloadLength + ", sequence number " + rtpPacket.Header.SequenceNumber + ", marker " + rtpPacket.Header.MarkerBit + "."); //Stopwatch sw = new Stopwatch(); //sw.Start(); _rtpSocket.SendTo(rtpBytes, _remoteEP); //sw.Stop(); //if (sw.ElapsedMilliseconds > 15) //{ // logger.Warn(" SendJpegFrame offset " + offset + ", payload length " + payloadLength + ", sequence number " + rtpPacket.Header.SequenceNumber + ", marker " + rtpPacket.Header.MarkerBit + ", took " + sw.ElapsedMilliseconds + "ms."); //} } //sw.Stop(); //System.Diagnostics.Debug.WriteLine("SendJpegFrame took " + sw.ElapsedMilliseconds + "."); } } catch (Exception excp) { if (IsRunning) { logger.Warn("Exception RTPChannel.SendJpegFrame attempting to send to the RTP socket at " + _remoteEP + ". " + excp); //_rtpSocketError = SocketError.SocketError; if (OnRTPSocketDisconnected != null) { OnRTPSocketDisconnected(); } } } }
private void ProcessRTPPackets() { try { Thread.CurrentThread.Name = "rtspclient-rtp"; _lastRTPReceivedAt = DateTime.Now; _lastBWCalcAt = DateTime.Now; while (!_isClosed) { while (_rtspSession.HasRTPPacket()) { RTPPacket rtpPacket = _rtspSession.GetNextRTPPacket(); if (rtpPacket != null) { _lastRTPReceivedAt = DateTime.Now; _bytesSinceLastBWCalc += RTPHeader.MIN_HEADER_LEN + rtpPacket.Payload.Length; if (_rtpTrackingAction != null) { double bwCalcSeconds = DateTime.Now.Subtract(_lastBWCalcAt).TotalSeconds; if (bwCalcSeconds > BANDWIDTH_CALCULATION_SECONDS) { _lastBWCalc = _bytesSinceLastBWCalc * 8 / bwCalcSeconds; _lastFrameRate = _framesSinceLastCalc / bwCalcSeconds; _bytesSinceLastBWCalc = 0; _framesSinceLastCalc = 0; _lastBWCalcAt = DateTime.Now; } var abbrevURL = (_url.Length <= 50) ? _url : _url.Substring(0, 50); string rtpTrackingText = String.Format("Url: {0}\r\nRcvd At: {1}\r\nSeq Num: {2}\r\nTS: {3}\r\nPayoad: {4}\r\nFrame Size: {5}\r\nBW: {6}\r\nFrame Rate: {7}", abbrevURL, DateTime.Now.ToString("HH:mm:ss:fff"), rtpPacket.Header.SequenceNumber, rtpPacket.Header.Timestamp, ((SDPMediaFormatsEnum)rtpPacket.Header.PayloadType).ToString(), _lastFrameSize + " bytes", _lastBWCalc.ToString("0.#") + "bps", _lastFrameRate.ToString("0.##") + "fps"); _rtpTrackingAction(rtpTrackingText); } if (rtpPacket.Header.Timestamp < _lastCompleteFrameTimestamp) { System.Diagnostics.Debug.WriteLine("Ignoring RTP packet with timestamp " + rtpPacket.Header.Timestamp + " as it's earlier than the last complete frame."); } else { while (_frames.Count > MAX_FRAMES_QUEUE_LENGTH) { var oldestFrame = _frames.OrderBy(x => x.Timestamp).First(); _frames.Remove(oldestFrame); System.Diagnostics.Debug.WriteLine("Receive queue full, dropping oldest frame with timestamp " + oldestFrame.Timestamp + "."); } var frame = _frames.Where(x => x.Timestamp == rtpPacket.Header.Timestamp).SingleOrDefault(); if (frame == null) { frame = new RTPFrame() { Timestamp = rtpPacket.Header.Timestamp, HasMarker = rtpPacket.Header.MarkerBit == 1 }; frame.AddRTPPacket(rtpPacket); _frames.Add(frame); } else { frame.HasMarker = (rtpPacket.Header.MarkerBit == 1); frame.AddRTPPacket(rtpPacket); } if (frame.IsComplete) { // The frame is ready for handing over to the UI. byte[] imageBytes = frame.GetFramePayload(); _lastFrameSize = imageBytes.Length; _framesSinceLastCalc++; _lastCompleteFrameTimestamp = rtpPacket.Header.Timestamp; //System.Diagnostics.Debug.WriteLine("Frame ready " + frame.Timestamp + ", sequence numbers " + frame.StartSequenceNumber + " to " + frame.EndSequenceNumber + ", payload length " + imageBytes.Length + "."); //logger.Debug("Frame ready " + frame.Timestamp + ", sequence numbers " + frame.StartSequenceNumber + " to " + frame.EndSequenceNumber + ", payload length " + imageBytes.Length + "."); _frames.Remove(frame); // Also remove any earlier frames as we don't care about anything that's earlier than the current complete frame. foreach (var oldFrame in _frames.Where(x => x.Timestamp <= rtpPacket.Header.Timestamp).ToList()) { System.Diagnostics.Debug.WriteLine("Discarding old frame for timestamp " + oldFrame.Timestamp + "."); logger.Warn("Discarding old frame for timestamp " + oldFrame.Timestamp + "."); _frames.Remove(oldFrame); } if (OnFrameReady != null) { try { //if (frame.FramePackets.Count == 1) //{ // // REMOVE. // logger.Warn("Discarding frame as there should have been more than 1 RTP packets."); //} //else //{ //System.Diagnostics.Debug.WriteLine("RTP frame ready for timestamp " + frame.Timestamp + "."); OnFrameReady(this, frame); //} } catch (Exception frameReadyExcp) { logger.Error("Exception RTSPClient.ProcessRTPPackets OnFrameReady. " + frameReadyExcp); } } } } } } if (DateTime.Now.Subtract(_lastRTPReceivedAt).TotalSeconds > RTP_TIMEOUT_SECONDS) { logger.Warn("No RTP packets were received on RTSP session " + _rtspSession.SessionID + " for " + RTP_TIMEOUT_SECONDS + ". The session will now be closed."); Close(); } else { Thread.Sleep(1); } } } catch (Exception excp) { logger.Error("Exception RTSPClient.ProcessRTPPackets. " + excp); } }