/// <summary> /// Sends an audio frame where the payload size is less than the maximum RTP packet payload size. /// </summary> /// <param name="payload">The audio payload 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 SendAudioFrame(byte[] payload, uint frameSpacing, int payloadType) { try { if (!IsRunning) { Logger.Logger.Warn("SendAudioFrame cannot be called on a closed RTP channel."); } else if (_rtpSocketError != SocketError.Success) { Logger.Logger.Warn("SendAudioFrame was called for an RTP socket in an error state of " + _rtpSocketError + "."); } else { _timestamp = (_timestamp == 0) ? DateTimeToNptTimestamp32(DateTime.Now) : (_timestamp + frameSpacing) % UInt32.MaxValue; RTPPacket rtpPacket = new RTPPacket(payload.Length); rtpPacket.Header.SyncSource = _syncSource; rtpPacket.Header.SequenceNumber = _sequenceNumber++; rtpPacket.Header.Timestamp = _timestamp; rtpPacket.Header.MarkerBit = 1; rtpPacket.Header.PayloadType = payloadType; Buffer.BlockCopy(payload, 0, rtpPacket.Payload, 0, payload.Length); byte[] rtpBytes = rtpPacket.GetBytes(); //Stopwatch sw = new Stopwatch(); //sw.Start(); _rtpSocket.SendTo(rtpBytes, rtpBytes.Length, SocketFlags.None, _remoteEP); //sw.Stop(); //if (sw.ElapsedMilliseconds > 15) //{ // logger.Warn(" SendAudioFrame offset " + offset + ", payload length " + payloadLength + ", sequence number " + rtpPacket.Header.SequenceNumber + ", marker " + rtpPacket.Header.MarkerBit + ", took " + sw.ElapsedMilliseconds + "ms."); //} } } catch (Exception excp) { if (IsRunning) { Logger.Logger.Warn("Exception RTPChannel.SendAudioFrame attempting to send to the RTP socket at " + _remoteEP + ". " + excp); if (OnRTPSocketDisconnected != null) { OnRTPSocketDisconnected(); } } } }
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.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.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.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.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.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.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.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.Logger.Error("Exception RTP Send. " + excp.GetType() + ". ->" + excp.Message); if (excp.GetType() == typeof(SocketException)) { Logger.Logger.Error( "socket exception errorcode=" + ((SocketException)excp).ErrorCode + "."); } Logger.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.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.Logger.Warn("Shutting down RTPSink due to passing RTPMaxStayAlive time."); Shutdown(); StopListening = true; } #endregion } } catch (Exception excp) { Logger.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.Logger.Error("Exception RTPSink SenderClosed. ->" + 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(); } } } }