// RTSP Messages are OPTIONS, DESCRIBE, SETUP, PLAY etc private void RtspMessageReceived(object sender, Rtsp.RtspChunkEventArgs e) { RtspResponse message = e.Message as RtspResponse; if (message.OriginalRequest == null) { return; } Logger.Info("Received " + message.OriginalRequest.ToString()); if (message.OriginalRequest is RtspRequestOptions) { ProcessOptionsRequest(message); } else if (message.OriginalRequest is RtspRequestDescribe) { ProcessDescribeRequest(message); } else if (message.OriginalRequest is RtspRequestSetup) { ProcessSetupRequest(message); } else if (message.OriginalRequest is RtspRequestPlay) { ProcessPlayRequest(message); } }
public void RtpDataReceived(object sender, Rtsp.RtspChunkEventArgs chunkEventArgs) { try { RtspData rtpData = chunkEventArgs.Message as RtspData; if (IsRtcpGoodbye(rtpData)) { HandleRtcpGoodbye(); } // Check which channel the Data was received on. // eg the Video Channel, the Video Control Channel (RTCP) // In the future would also check the Audio Channel and Audio Control Channel if (rtpData.Channel == videoRTCPChannel) { Logger.Info("Received a RTCP message on channel " + rtpData.Channel); return; } if (rtpData.Channel == videoDataChannel) { ProcessRTPVideo(chunkEventArgs); } } catch (Exception e) { Logger.Info(e.ToString()); rtspErrorSubject.OnNext(e.Message); } }
/// <summary> /// Raises the <see cref="E:DataReceived"/> event. /// </summary> /// <param name="rtspChunkEventArgs">The <see cref="Rtsp.RtspChunkEventArgs"/> instance containing the event data.</param> protected void OnDataReceived(Rtsp.RtspChunkEventArgs rtspChunkEventArgs) { EventHandler <Rtsp.RtspChunkEventArgs> handler = DataReceived; if (handler != null) { handler(this, rtspChunkEventArgs); } }
private void ProcessRTPVideo(Rtsp.RtspChunkEventArgs e) { // Received some Video Data on the correct channel. // RTP Packet Header // 0 - Version, P, X, CC, M, PT and Sequence Number //32 - Timestamp //64 - SSRC //96 - CSRCs (optional) //nn - Extension ID and Length //nn - Extension header int rtpVersion = (e.Message.Data[0] >> 6); int rtpPadding = (e.Message.Data[0] >> 5) & 0x01; int rtpRxtension = (e.Message.Data[0] >> 4) & 0x01; int rtpCSRCCount = (e.Message.Data[0] >> 0) & 0x0F; int rtpMarker = (e.Message.Data[1] >> 7) & 0x01; int rtpPayloadType = (e.Message.Data[1] >> 0) & 0x7F; uint rtpSequenceNumber = ((uint)e.Message.Data[2] << 8) + (uint)(e.Message.Data[3]); uint rtpTimestamp = ((uint)e.Message.Data[4] << 24) + (uint)(e.Message.Data[5] << 16) + (uint)(e.Message.Data[6] << 8) + (uint)(e.Message.Data[7]); uint rtpSSRC = ((uint)e.Message.Data[8] << 24) + (uint)(e.Message.Data[9] << 16) + (uint)(e.Message.Data[10] << 8) + (uint)(e.Message.Data[11]); int rtpPayloadStart = 4 // V,P,M,SEQ + 4 // time stamp + 4 // ssrc + (4 * rtpCSRCCount); // zero or more csrcs if (rtpRxtension == 1) { uint rtpExtensionId = ((uint)e.Message.Data[rtpPayloadStart + 0] << 8) + (uint)(e.Message.Data[rtpPayloadStart + 1] << 0); uint rtpExtensionSize = ((uint)e.Message.Data[rtpPayloadStart + 2] << 8) + (uint)(e.Message.Data[rtpPayloadStart + 3] << 0) * 4; // units of extension_size is 4-bytes rtpPayloadStart += 4 + (int)rtpExtensionSize; // extension header and extension payload } //Logger.Info("RTP Data" // + " V=" + rtpVersion // + " P=" + rtpPadding // + " X=" + rtpRxtension // + " CC=" + rtpCSRCCount // + " M=" + rtpMarker // + " PT=" + rtpPayloadType // + " Seq=" + rtpSequenceNumber // + " Time (MS)=" + rtpTimestamp / 90 // convert from 90kHZ clock to ms // + " SSRC=" + rtpSSRC // + " Size=" + e.Message.Data.Length); // Check the payload type in the RTP packet matches the Payload Type value from the SDP if (videoPayloadType > 0 && rtpPayloadType != videoPayloadType) { Logger.Info("Ignoring this RTP payload: " + rtpPayloadType); return; // ignore this data } byte[] rtp_payload = new byte[e.Message.Data.Length - rtpPayloadStart]; // payload with RTP header removed Array.Copy(e.Message.Data, rtpPayloadStart, rtp_payload, 0, rtp_payload.Length); // copy payload chunkReadySubject.OnNext(rtp_payload); }
public void RtpDataReceived(object sender, Rtsp.RtspChunkEventArgs e) { RtspData rtpData = e.Message as RtspData; // Check which channel the Data was received on. // eg the Video Channel, the Video Control Channel (RTCP) // In the future would also check the Audio Channel and Audio Control Channel if (rtpData.Channel == videoRTCPChannel) { Logger.Info("Received a RTCP message on channel " + rtpData.Channel); return; } if (rtpData.Channel == videoDataChannel) { ProcessRTPVideo(e); } }
// RTSP Messages are OPTIONS, DESCRIBE, SETUP, PLAY etc private void RtspMessageReceived(object sender, Rtsp.RtspChunkEventArgs chunkEventArgs) { RtspResponse message = chunkEventArgs.Message as RtspResponse; if (message?.OriginalRequest == null) { return; } Logger.Info("Received " + message.OriginalRequest); try { if (message.OriginalRequest is RtspRequestOptions) { ProcessOptionsRequest(message); } else if (message.OriginalRequest is RtspRequestDescribe) { ProcessDescribeRequest(message); } else if (message.OriginalRequest is RtspRequestSetup) { ProcessSetupRequest(message); } else if (message.OriginalRequest is RtspRequestPlay) { ProcessPlayRequest(message); } } catch (Exception e) { Logger.Info(e.ToString()); rtspErrorSubject.OnNext("RTSP error occured."); } }
// RTSP Messages are OPTIONS, DESCRIBE, SETUP, PLAY etc private void Rtsp_MessageReceived(object sender, Rtsp.RtspChunkEventArgs e) { Rtsp.Messages.RtspResponse message = e.Message as Rtsp.Messages.RtspResponse; Console.WriteLine("Received " + message.OriginalRequest.ToString()); // Check if the Message has an Authenticate header. If so we update the 'realm' and 'nonce' if (message.Headers.ContainsKey(RtspHeaderNames.WWWAuthenticate)) { String www_authenticate = message.Headers[RtspHeaderNames.WWWAuthenticate]; // Parse www_authenticate // EG: Digest realm="AXIS_WS_ACCC8E3A0A8F", nonce="000057c3Y810622bff50b36005eb5efeae118626a161bf", stale=FALSE string[] items = www_authenticate.Split(new char[] { ',', ' ' }); foreach (string item in items) { // Split on the = symbol and load in string[] parts = item.Split(new char[] { '=' }); if (parts.Count() >= 2 && parts[0].Trim().Equals("realm")) { realm = parts[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes } else if (parts.Count() >= 2 && parts[0].Trim().Equals("nonce")) { nonce = parts[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes } } Console.WriteLine("WWW Authorize parsed for " + realm + " " + nonce); } // If we get a reply to OPTIONS and CSEQ is 1 (which was our first command), then send the DESCRIBE // If we fer a reply to OPTIONS and CSEQ is not 1, it must have been a keepalive command if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestOptions) { if (message.CSeq == 1) { // Start a Timer to send an OPTIONS command (for keepalive) every 20 seconds keepalive_timer = new System.Timers.Timer(); keepalive_timer.Elapsed += Timer_Elapsed; keepalive_timer.Interval = 20 * 1000; keepalive_timer.Enabled = true; // send the DESCRIBE. First time around we have no WWW-Authorise Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); rtsp_client.SendMessage(describe_message); } else { // do nothing } } // If we get a reply to DESCRIBE (which was our second command), then prosess SDP and send the SETUP if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestDescribe) { // Got a reply for DESCRIBE // First time we send DESCRIBE we will not have the authorization Nonce so we // handle the Unauthorized 401 error here and send a new DESCRIBE message if (message.IsOk == false) { Console.WriteLine("Got Error in DESCRIBE Reply " + message.ReturnCode + " " + message.ReturnMessage); if (message.ReturnCode == 401 && (message.OriginalRequest.Headers.ContainsKey(RtspHeaderNames.Authorization) == false)) { // Error 401 - Unauthorized, but the request did not use Authorizarion. if (username == null || password == null) { // we do nothave a username or password. Abort return; } // Send a new DESCRIBE with authorization String digest_authorization = GenerateDigestAuthorization(username, password, realm, nonce, url, "DESCRIBE"); Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); if (digest_authorization != null) { describe_message.Headers.Add(RtspHeaderNames.Authorization, digest_authorization); } rtsp_client.SendMessage(describe_message); return; } else if (message.ReturnCode == 401 && (message.OriginalRequest.Headers.ContainsKey(RtspHeaderNames.Authorization) == true)) { // Authorization failed return; } else { // some other error return; } } // Examine the SDP Console.Write(System.Text.Encoding.UTF8.GetString(message.Data)); Rtsp.Sdp.SdpFile sdp_data; using (StreamReader sdp_stream = new StreamReader(new MemoryStream(message.Data))) { sdp_data = Rtsp.Sdp.SdpFile.Read(sdp_stream); } // Process each 'Media' Attribute in the SDP (each sub-stream) for (int x = 0; x < sdp_data.Medias.Count; x++) { bool audio = (sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.audio); bool video = (sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.video); if (video && video_payload != -1) { continue; // have already matched an video payload } if (audio && audio_payload != -1) { continue; // have already matched an audio payload } if (audio || video) { // search the attributes for control, rtpmap and fmtp // (fmtp only applies to video) String control = ""; // the "track" or "stream id" Rtsp.Sdp.AttributFmtp fmtp = null; // holds SPS and PPS in base64 (h264 video) foreach (Rtsp.Sdp.Attribut attrib in sdp_data.Medias[x].Attributs) { if (attrib.Key.Equals("control")) { String sdp_control = attrib.Value; if (sdp_control.ToLower().StartsWith("rtsp://")) { control = sdp_control; //absolute path } else { control = url + "/" + sdp_control; // relative path } } if (attrib.Key.Equals("fmtp")) { fmtp = attrib as Rtsp.Sdp.AttributFmtp; } if (attrib.Key.Equals("rtpmap")) { Rtsp.Sdp.AttributRtpMap rtpmap = attrib as Rtsp.Sdp.AttributRtpMap; // Check if the Codec Used (EncodingName) is one we support String[] valid_video_codecs = { "H264" }; String[] valid_audio_codecs = { "PCMA", "PCMU" }; if (video && Array.IndexOf(valid_video_codecs, rtpmap.EncodingName) >= 0) { // found a valid codec video_codec = rtpmap.EncodingName; video_payload = sdp_data.Medias[x].PayloadType; } if (audio && Array.IndexOf(valid_audio_codecs, rtpmap.EncodingName) >= 0) { audio_codec = rtpmap.EncodingName; audio_payload = sdp_data.Medias[x].PayloadType; } } } // If the rtpmap contains H264 then split the fmtp to get the sprop-parameter-sets which hold the SPS and PPS in base64 if (video && video_codec.Contains("H264") && fmtp != null) { var param = Rtsp.Sdp.H264Parameters.Parse(fmtp.FormatParameter); var sps_pps = param.SpropParameterSets; if (sps_pps.Count() >= 2) { byte[] sps = sps_pps[0]; byte[] pps = sps_pps[1]; if (Received_SPS_PPS != null) { Received_SPS_PPS(sps, pps); } } } // Send the SETUP RTSP command if we have a matching Payload Decoder if (video && video_payload == -1) { continue; } if (audio && audio_payload == -1) { continue; } RtspTransport transport = null; int data_channel = 0; int rtcp_channel = 0; if (rtp_transport == RTP_TRANSPORT.TCP) { // Server interleaves the RTP packets over the RTSP connection // Example for TCP mode (RTP over RTSP) Transport: RTP/AVP/TCP;interleaved=0-1 if (video) { video_data_channel = 0; video_rtcp_channel = 1; data_channel = video_data_channel; rtcp_channel = video_rtcp_channel; } if (audio) { audio_data_channel = 2; audio_rtcp_channel = 3; data_channel = audio_data_channel; rtcp_channel = audio_rtcp_channel; } transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(data_channel, rtcp_channel), // Eg Channel 0 for video. Channel 1 for RTCP status reports }; } if (rtp_transport == RTP_TRANSPORT.UDP) { // Server sends the RTP packets to a Pair of UDP Ports (one for data, one for rtcp control messages) // Example for UDP mode Transport: RTP/AVP;unicast;client_port=8000-8001 if (video) { video_data_channel = video_udp_pair.data_port; // Used in DataReceived event handler video_rtcp_channel = video_udp_pair.control_port; // Used in DataReceived event handler data_channel = video_data_channel; rtcp_channel = video_rtcp_channel; } if (audio) { audio_data_channel = audio_udp_pair.data_port; // Used in DataReceived event handler audio_rtcp_channel = audio_udp_pair.control_port; // Used in DataReceived event handler data_channel = audio_data_channel; rtcp_channel = audio_rtcp_channel; } transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = false, ClientPort = new PortCouple(data_channel, rtcp_channel), // a Channel for data (video or audio). a Channel for RTCP status reports }; } if (rtp_transport == RTP_TRANSPORT.MULTICAST) { // Server sends the RTP packets to a Pair of UDP ports (one for data, one for rtcp control messages) // using Multicast Address and Ports that are in the reply to the SETUP message // Example for MULTICAST mode Transport: RTP/AVP;multicast if (video) { video_data_channel = 0; // we get this information in the SETUP message reply video_rtcp_channel = 0; // we get this information in the SETUP message reply } if (audio) { audio_data_channel = 0; // we get this information in the SETUP message reply audio_rtcp_channel = 0; // we get this information in the SETUP message reply } transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = true }; } // Add authorization (if there is a username and password) String digest_authorization = GenerateDigestAuthorization(username, password, realm, nonce, url, "SETUP"); // Send SETUP Rtsp.Messages.RtspRequestSetup setup_message = new Rtsp.Messages.RtspRequestSetup(); setup_message.RtspUri = new Uri(control); setup_message.AddTransport(transport); if (digest_authorization != null) { setup_message.Headers.Add("Authorization", digest_authorization); } rtsp_client.SendMessage(setup_message); } } } // If we get a reply to SETUP (which was our third command), then process and then send PLAY if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestSetup) { // Got Reply to SETUP if (message.IsOk == false) { Console.WriteLine("Got Error in SETUP Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } Console.WriteLine("Got reply from Setup. Session is " + message.Session); session = message.Session; // Session value used with Play, Pause, Teardown // Check the Transport header if (message.Headers.ContainsKey(RtspHeaderNames.Transport)) { RtspTransport transport = RtspTransport.Parse(message.Headers[RtspHeaderNames.Transport]); // Check if Transport header includes Multicast if (transport.IsMulticast) { String multicast_address = transport.Destination; video_data_channel = transport.Port.First; video_rtcp_channel = transport.Port.Second; // Create the Pair of UDP Sockets in Multicast mode video_udp_pair = new Rtsp.UDPSocket(multicast_address, video_data_channel, multicast_address, video_rtcp_channel); video_udp_pair.DataReceived += Rtp_DataReceived; video_udp_pair.Start(); // TODO - Need to set audio_udp_pair } } // Send PLAY Rtsp.Messages.RtspRequest play_message = new Rtsp.Messages.RtspRequestPlay(); play_message.RtspUri = new Uri(url); play_message.Session = session; rtsp_client.SendMessage(play_message); } // If we get a reply to PLAY (which was our fourth command), then we should have video being received if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestPlay) { // Got Reply to PLAY if (message.IsOk == false) { Console.WriteLine("Got Error in PLAY Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } Console.WriteLine("Got reply from Play " + message.Command); } }
int rtp_count = 0; // used for statistics // RTP packet (or RTCP packet) has been received. public void Rtp_DataReceived(object sender, Rtsp.RtspChunkEventArgs e) { Rtsp.Messages.RtspData data_received = e.Message as Rtsp.Messages.RtspData; // Check which channel the Data was received on. // eg the Video Channel, the Video Control Channel (RTCP) // the Audio Channel or the Audio Control Channel (RTCP) if (data_received.Channel == video_rtcp_channel || data_received.Channel == audio_rtcp_channel) { Console.WriteLine("Received a RTCP message on channel " + data_received.Channel); // RTCP Packet // - Version, Padding and Receiver Report Count // - Packet Type // - Length // - SSRC // - payload // There can be multiple RTCP packets transmitted together. Loop ever each one long packetIndex = 0; while (packetIndex < e.Message.Data.Length) { int rtcp_version = (e.Message.Data[packetIndex + 0] >> 6); int rtcp_padding = (e.Message.Data[packetIndex + 0] >> 5) & 0x01; int rtcp_reception_report_count = (e.Message.Data[packetIndex + 0] & 0x1F); byte rtcp_packet_type = e.Message.Data[packetIndex + 1]; // Values from 200 to 207 uint rtcp_length = ((uint)e.Message.Data[packetIndex + 2] << 8) + (uint)(e.Message.Data[packetIndex + 3]); // number of 32 bit words uint rtcp_ssrc = ((uint)e.Message.Data[packetIndex + 4] << 24) + (uint)(e.Message.Data[packetIndex + 5] << 16) + (uint)(e.Message.Data[packetIndex + 6] << 8) + (uint)(e.Message.Data[packetIndex + 7]); // 200 = SR = Sender Report // 201 = RR = Receiver Report // 202 = SDES = Source Description // 203 = Bye = Goodbye // 204 = APP = Application Specific Method // 207 = XR = Extended Reports Console.WriteLine("RTCP Data. PacketType=" + rtcp_packet_type + " SSRC=" + rtcp_ssrc); if (rtcp_packet_type == 200) { // Send a Receiver Report try { byte[] rtcp_receiver_report = new byte[8]; int version = 2; int paddingBit = 0; int reportCount = 0; // an empty report int packetType = 201; // Receiver Report int length = (rtcp_receiver_report.Length / 4) - 1; // num 32 bit words minus 1 rtcp_receiver_report[0] = (byte)((version << 6) + (paddingBit << 5) + reportCount); rtcp_receiver_report[1] = (byte)(packetType); rtcp_receiver_report[2] = (byte)((length >> 8) & 0xFF); rtcp_receiver_report[3] = (byte)((length >> 0) & 0XFF); rtcp_receiver_report[4] = (byte)((ssrc >> 24) & 0xFF); rtcp_receiver_report[5] = (byte)((ssrc >> 16) & 0xFF); rtcp_receiver_report[6] = (byte)((ssrc >> 8) & 0xFF); rtcp_receiver_report[7] = (byte)((ssrc >> 0) & 0xFF); if (rtp_transport == RTP_TRANSPORT.TCP) { // Send it over via the RTSP connection rtsp_client.SendData(video_rtcp_channel, rtcp_receiver_report); } if (rtp_transport == RTP_TRANSPORT.UDP || rtp_transport == RTP_TRANSPORT.MULTICAST) { // Send it via a UDP Packet Console.WriteLine("TODO - Need to implement RTCP over UDP"); } } catch { Console.WriteLine("Error writing RTCP packet"); } } packetIndex = packetIndex + ((rtcp_length + 1) * 4); } return; } if (data_received.Channel == video_data_channel || data_received.Channel == audio_data_channel) { // Received some Video or Audio Data on the correct channel. // RTP Packet Header // 0 - Version, P, X, CC, M, PT and Sequence Number //32 - Timestamp //64 - SSRC //96 - CSRCs (optional) //nn - Extension ID and Length //nn - Extension header int rtp_version = (e.Message.Data[0] >> 6); int rtp_padding = (e.Message.Data[0] >> 5) & 0x01; int rtp_extension = (e.Message.Data[0] >> 4) & 0x01; int rtp_csrc_count = (e.Message.Data[0] >> 0) & 0x0F; int rtp_marker = (e.Message.Data[1] >> 7) & 0x01; int rtp_payload_type = (e.Message.Data[1] >> 0) & 0x7F; uint rtp_sequence_number = ((uint)e.Message.Data[2] << 8) + (uint)(e.Message.Data[3]); uint rtp_timestamp = ((uint)e.Message.Data[4] << 24) + (uint)(e.Message.Data[5] << 16) + (uint)(e.Message.Data[6] << 8) + (uint)(e.Message.Data[7]); uint rtp_ssrc = ((uint)e.Message.Data[8] << 24) + (uint)(e.Message.Data[9] << 16) + (uint)(e.Message.Data[10] << 8) + (uint)(e.Message.Data[11]); int rtp_payload_start = 4 // V,P,M,SEQ + 4 // time stamp + 4 // ssrc + (4 * rtp_csrc_count); // zero or more csrcs uint rtp_extension_id = 0; uint rtp_extension_size = 0; if (rtp_extension == 1) { rtp_extension_id = ((uint)e.Message.Data[rtp_payload_start + 0] << 8) + (uint)(e.Message.Data[rtp_payload_start + 1] << 0); rtp_extension_size = ((uint)e.Message.Data[rtp_payload_start + 2] << 8) + (uint)(e.Message.Data[rtp_payload_start + 3] << 0) * 4; // units of extension_size is 4-bytes rtp_payload_start += 4 + (int)rtp_extension_size; // extension header and extension payload } Console.WriteLine("RTP Data" + " V=" + rtp_version + " P=" + rtp_padding + " X=" + rtp_extension + " CC=" + rtp_csrc_count + " M=" + rtp_marker + " PT=" + rtp_payload_type + " Seq=" + rtp_sequence_number + " Time (MS)=" + rtp_timestamp / 90 // convert from 90kHZ clock to ms + " SSRC=" + rtp_ssrc + " Size=" + e.Message.Data.Length); // Check the payload type in the RTP packet matches the Payload Type value from the SDP if (data_received.Channel == video_data_channel && rtp_payload_type != video_payload) { Console.WriteLine("Ignoring this Video RTP payload"); return; // ignore this data } // Check the payload type in the RTP packet matches the Payload Type value from the SDP else if (data_received.Channel == audio_data_channel && rtp_payload_type != audio_payload) { Console.WriteLine("Ignoring this Audio RTP payload"); return; // ignore this data } else if (data_received.Channel == video_data_channel && rtp_payload_type >= 96 && rtp_payload_type <= 127 && video_codec.Equals("H264")) { // H264 RTP Packet // If rtp_marker is '1' then this is the final transmission for this packet. // If rtp_marker is '0' we need to accumulate data with the same timestamp // ToDo - Check Timestamp // Add the RTP packet to the tempoary_rtp list until we have a complete 'Frame' byte[] rtp_payload = new byte[e.Message.Data.Length - rtp_payload_start]; // payload with RTP header removed System.Array.Copy(e.Message.Data, rtp_payload_start, rtp_payload, 0, rtp_payload.Length); // copy payload List <byte[]> nal_units = h264Payload.Process_H264_RTP_Packet(rtp_payload, rtp_marker); // this will cache the Packets until there is a Frame if (nal_units == null) { // we have not passed in enough RTP packets to make a Frame of video } else { // we have a frame of NAL Units. Write them to the file if (Received_NALs != null) { Received_NALs(nal_units); } } } else if (data_received.Channel == audio_data_channel && (rtp_payload_type == 0 || rtp_payload_type == 8 || audio_codec.Equals("PCMA") || audio_codec.Equals("PCMU"))) { // G711 PCMA or G711 PCMU byte[] rtp_payload = new byte[e.Message.Data.Length - rtp_payload_start]; // payload with RTP header removed System.Array.Copy(e.Message.Data, rtp_payload_start, rtp_payload, 0, rtp_payload.Length); // copy payload List <byte[]> audio_frames = g711Payload.Process_G711_RTP_Packet(rtp_payload, rtp_marker); if (audio_frames == null) { // some error } else { // Write the audio frames to the file if (Received_G711 != null) { Received_G711(audio_codec, audio_frames); } } } else if (data_received.Channel == video_data_channel && rtp_payload_type == 26) { Console.WriteLine("No parser has been written for JPEG RTP packets. Please help write one"); return; // ignore this data } else { Console.WriteLine("No parser for RTP payload " + rtp_payload_type); } } }
// RTSP Messages are OPTIONS, DESCRIBE, SETUP, PLAY etc private void Rtsp_MessageReceived(object sender, Rtsp.RtspChunkEventArgs e) { Rtsp.Messages.RtspResponse message = e.Message as Rtsp.Messages.RtspResponse; Console.WriteLine("Received " + message.OriginalRequest.ToString()); // If message has a 401 - Unauthorised Error, then we re-send the message with Authorization // using the most recently received 'realm' and 'nonce' if (message.IsOk == false) { Console.WriteLine("Got Error in RTSP Reply " + message.ReturnCode + " " + message.ReturnMessage); if (message.ReturnCode == 401 && (message.OriginalRequest.Headers.ContainsKey(RtspHeaderNames.Authorization) == true)) { // the authorization failed. Stop(); return; } // Check if the Reply has an Authenticate header. if (message.ReturnCode == 401 && message.Headers.ContainsKey(RtspHeaderNames.WWWAuthenticate)) { // Process the WWW-Authenticate header // EG: Basic realm="AProxy" // EG: Digest realm="AXIS_WS_ACCC8E3A0A8F", nonce="000057c3Y810622bff50b36005eb5efeae118626a161bf", stale=FALSE String www_authenticate = message.Headers[RtspHeaderNames.WWWAuthenticate]; string[] items = www_authenticate.Split(new char[] { ',', ' ' }); foreach (string item in items) { if (item.ToLower().Equals("basic")) { auth_type = "Basic"; } else if (item.ToLower().Equals("digest")) { auth_type = "Digest"; } else { // Split on the = symbol and update the realm and nonce string[] parts = item.Split(new char[] { '=' }, 2); // max 2 parts in the results array if (parts.Count() >= 2 && parts[0].Trim().Equals("realm")) { realm = parts[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes } else if (parts.Count() >= 2 && parts[0].Trim().Equals("nonce")) { nonce = parts[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes } } } Console.WriteLine("WWW Authorize parsed for " + auth_type + " " + realm + " " + nonce); } RtspMessage resend_message = message.OriginalRequest.Clone() as RtspMessage; if (auth_type != null) { AddAuthorization(resend_message, username, password, auth_type, realm, nonce, url); } rtsp_client.SendMessage(resend_message); return; } // If we get a reply to OPTIONS then start the Keepalive Timer and send DESCRIBE if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestOptions) { if (keepalive_timer == null) { // Start a Timer to send an OPTIONS command (for keepalive) every 20 seconds keepalive_timer = new System.Timers.Timer(); keepalive_timer.Elapsed += Timer_Elapsed; keepalive_timer.Interval = 20 * 1000; keepalive_timer.Enabled = true; // Send DESCRIBE Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); if (auth_type != null) { AddAuthorization(describe_message, username, password, auth_type, realm, nonce, url); } rtsp_client.SendMessage(describe_message); } else { // do nothing } } // If we get a reply to DESCRIBE (which was our second command), then prosess SDP and send the SETUP if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestDescribe) { // Got a reply for DESCRIBE if (message.IsOk == false) { Console.WriteLine("Got Error in DESCRIBE Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } // Examine the SDP Console.Write(System.Text.Encoding.UTF8.GetString(message.Data)); Rtsp.Sdp.SdpFile sdp_data; using (StreamReader sdp_stream = new StreamReader(new MemoryStream(message.Data))) { sdp_data = Rtsp.Sdp.SdpFile.Read(sdp_stream); } // RTP and RTCP 'channels' are used in TCP Interleaved mode (RTP over RTSP) int next_free_rtp_channel = 0; int next_free_rtcp_channel = 1; // Process each 'Media' Attribute in the SDP (each sub-stream) for (int x = 0; x < sdp_data.Medias.Count; x++) { bool audio = (sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.audio); bool video = (sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.video); if (video && video_payload != -1) { continue; // have already matched an video payload } if (audio && audio_payload != -1) { continue; // have already matched an audio payload } if (audio || video) { // search the attributes for control, rtpmap and fmtp // (fmtp only applies to video) String control = ""; // the "track" or "stream id" Rtsp.Sdp.AttributFmtp fmtp = null; // holds SPS and PPS in base64 (h264 video) foreach (Rtsp.Sdp.Attribut attrib in sdp_data.Medias[x].Attributs) { if (attrib.Key.Equals("control")) { String sdp_control = attrib.Value; if (sdp_control.ToLower().StartsWith("rtsp://")) { control = sdp_control; //absolute path } else { control = url + "/" + sdp_control; // relative path } } if (attrib.Key.Equals("fmtp")) { fmtp = attrib as Rtsp.Sdp.AttributFmtp; } if (attrib.Key.Equals("rtpmap")) { Rtsp.Sdp.AttributRtpMap rtpmap = attrib as Rtsp.Sdp.AttributRtpMap; // Check if the Codec Used (EncodingName) is one we support String[] valid_video_codecs = { "H264" }; String[] valid_audio_codecs = { "PCMA", "PCMU", "AMR" }; if (video && Array.IndexOf(valid_video_codecs, rtpmap.EncodingName) >= 0) { // found a valid codec video_codec = rtpmap.EncodingName; video_payload = sdp_data.Medias[x].PayloadType; } if (audio && Array.IndexOf(valid_audio_codecs, rtpmap.EncodingName) >= 0) { audio_codec = rtpmap.EncodingName; audio_payload = sdp_data.Medias[x].PayloadType; } } } // If the rtpmap contains H264 then split the fmtp to get the sprop-parameter-sets which hold the SPS and PPS in base64 if (video && video_codec.Contains("H264") && fmtp != null) { var param = Rtsp.Sdp.H264Parameters.Parse(fmtp.FormatParameter); var sps_pps = param.SpropParameterSets; if (sps_pps.Count() >= 2) { byte[] sps = sps_pps[0]; byte[] pps = sps_pps[1]; if (Received_SPS_PPS != null) { Received_SPS_PPS(sps, pps); } } } // Send the SETUP RTSP command if we have a matching Payload Decoder if (video && video_payload == -1) { continue; } if (audio && audio_payload == -1) { continue; } RtspTransport transport = null; if (rtp_transport == RTP_TRANSPORT.TCP) { // Server interleaves the RTP packets over the RTSP connection // Example for TCP mode (RTP over RTSP) Transport: RTP/AVP/TCP;interleaved=0-1 if (video) { video_data_channel = next_free_rtp_channel; video_rtcp_channel = next_free_rtcp_channel; } if (audio) { audio_data_channel = next_free_rtp_channel; audio_rtcp_channel = next_free_rtcp_channel; } transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(next_free_rtp_channel, next_free_rtcp_channel), // Eg Channel 0 for RTP video data. Channel 1 for RTCP status reports }; next_free_rtp_channel += 2; next_free_rtcp_channel += 2; } if (rtp_transport == RTP_TRANSPORT.UDP) { int rtp_port = 0; int rtcp_port = 0; // Server sends the RTP packets to a Pair of UDP Ports (one for data, one for rtcp control messages) // Example for UDP mode Transport: RTP/AVP;unicast;client_port=8000-8001 if (video) { video_data_channel = video_udp_pair.data_port; // Used in DataReceived event handler video_rtcp_channel = video_udp_pair.control_port; // Used in DataReceived event handler rtp_port = video_udp_pair.data_port; rtcp_port = video_udp_pair.control_port; } if (audio) { audio_data_channel = audio_udp_pair.data_port; // Used in DataReceived event handler audio_rtcp_channel = audio_udp_pair.control_port; // Used in DataReceived event handler rtp_port = audio_udp_pair.data_port; rtcp_port = audio_udp_pair.control_port; } transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = false, ClientPort = new PortCouple(rtp_port, rtcp_port), // a UDP Port for data (video or audio). a UDP Port for RTCP status reports }; } if (rtp_transport == RTP_TRANSPORT.MULTICAST) { // Server sends the RTP packets to a Pair of UDP ports (one for data, one for rtcp control messages) // using Multicast Address and Ports that are in the reply to the SETUP message // Example for MULTICAST mode Transport: RTP/AVP;multicast if (video) { video_data_channel = 0; // we get this information in the SETUP message reply video_rtcp_channel = 0; // we get this information in the SETUP message reply } if (audio) { audio_data_channel = 0; // we get this information in the SETUP message reply audio_rtcp_channel = 0; // we get this information in the SETUP message reply } transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = true }; } // Generate SETUP messages Rtsp.Messages.RtspRequestSetup setup_message = new Rtsp.Messages.RtspRequestSetup(); setup_message.RtspUri = new Uri(control); setup_message.AddTransport(transport); if (auth_type != null) { AddAuthorization(setup_message, username, password, auth_type, realm, nonce, url); } // Add SETUP message to list of mesages to send setup_messages.Add(setup_message); } } // Send the FIRST SETUP message and remove it from the list of Setup Messages rtsp_client.SendMessage(setup_messages[0]); setup_messages.RemoveAt(0); } // If we get a reply to SETUP (which was our third command), then we // (i) check if we have any more SETUP commands to send out (eg if we are doing SETUP for Video and Audio) // (ii) send a PLAY command if all the SETUP command have been sent if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestSetup) { // Got Reply to SETUP if (message.IsOk == false) { Console.WriteLine("Got Error in SETUP Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } Console.WriteLine("Got reply from Setup. Session is " + message.Session); session = message.Session; // Session value used with Play, Pause, Teardown and and additional Setups // Check the Transport header if (message.Headers.ContainsKey(RtspHeaderNames.Transport)) { RtspTransport transport = RtspTransport.Parse(message.Headers[RtspHeaderNames.Transport]); // Check if Transport header includes Multicast if (transport.IsMulticast) { String multicast_address = transport.Destination; video_data_channel = transport.Port.First; video_rtcp_channel = transport.Port.Second; // Create the Pair of UDP Sockets in Multicast mode video_udp_pair = new Rtsp.UDPSocket(multicast_address, video_data_channel, multicast_address, video_rtcp_channel); video_udp_pair.DataReceived += Rtp_DataReceived; video_udp_pair.Start(); // TODO - Need to set audio_udp_pair } } // Check if we have another SETUP command to send, then remote it from the list if (setup_messages.Count > 0) { // send the next SETUP message, after adding in the 'session' Rtsp.Messages.RtspRequestSetup next_setup = setup_messages[0]; next_setup.Session = session; rtsp_client.SendMessage(next_setup); setup_messages.RemoveAt(0); } else { // Send PLAY Rtsp.Messages.RtspRequest play_message = new Rtsp.Messages.RtspRequestPlay(); play_message.RtspUri = new Uri(url); play_message.Session = session; if (auth_type != null) { AddAuthorization(play_message, username, password, auth_type, realm, nonce, url); } rtsp_client.SendMessage(play_message); } } // If we get a reply to PLAY (which was our fourth command), then we should have video being received if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestPlay) { // Got Reply to PLAY if (message.IsOk == false) { Console.WriteLine("Got Error in PLAY Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } Console.WriteLine("Got reply from Play " + message.Command); } }
private void Rtsp_client_DataReceived(object sender, Rtsp.RtspChunkEventArgs e) { // RTP Packet Header // 0 - Version, P, X, CC, M, PT and Sequence Number //32 - Timestamp //64 - SSRC //96 - CSRCs (optional) //nn - Extension ID and Length //nn - Extension header int rtp_version = (e.Message.Data[0] >> 6); int rtp_padding = (e.Message.Data[0] >> 5) & 0x01; int rtp_extension = (e.Message.Data[0] >> 4) & 0x01; int rtp_csrc_count = (e.Message.Data[0] >> 0) & 0x0F; int rtp_marker = (e.Message.Data[1] >> 7) & 0x01; int rtp_payload_type = (e.Message.Data[1] >> 0) & 0x7F; uint rtp_sequence_number = ((uint)e.Message.Data[2] << 8) + (uint)(e.Message.Data[3]); uint rtp_timestamp = ((uint)e.Message.Data[4] << 24) + (uint)(e.Message.Data[5] << 16) + (uint)(e.Message.Data[6] << 8) + (uint)(e.Message.Data[7]); uint rtp_ssrc = ((uint)e.Message.Data[8] << 24) + (uint)(e.Message.Data[9] << 16) + (uint)(e.Message.Data[10] << 8) + (uint)(e.Message.Data[11]); int rtp_payload_start = 4 // V,P,M,SEQ + 4 // time stamp + 4 // ssrc + (4 * rtp_csrc_count); // zero or more csrcs uint rtp_extension_id = 0; uint rtp_extension_size = 0; if (rtp_extension == 1) { rtp_extension_id = ((uint)e.Message.Data[rtp_payload_start + 0] << 8) + (uint)(e.Message.Data[rtp_payload_start + 1] << 0); rtp_extension_size = ((uint)e.Message.Data[rtp_payload_start + 2] << 8) + (uint)(e.Message.Data[rtp_payload_start + 3] << 0); rtp_payload_start += 4 + (int)rtp_extension_size; // extension header and extension payload } Console.WriteLine("RTP Data" + " V=" + rtp_version + " P=" + rtp_padding + " X=" + rtp_extension + " CC=" + rtp_csrc_count + " M=" + rtp_marker + " PT=" + rtp_payload_type + " Seq=" + rtp_sequence_number + " Time=" + rtp_timestamp + " SSRC=" + rtp_ssrc + " Size=" + e.Message.Data.Length); if (rtp_payload_type != video_payload) { Console.WriteLine("Ignoring this RTP payload"); return; // ignore this data } // If rtp_marker is '1' then this is the final transmission for this packet. // If rtp_marker is '0' we need to accumulate data with the same timestamp // ToDo - Check Timestamp // ToDo - could avoid a copy if there is only one RTP frame for the data (temp list is zero) // Add to the tempoary_rtp List byte[] rtp_payload = new byte[e.Message.Data.Length - rtp_payload_start]; // payload with RTP header removed System.Array.Copy(e.Message.Data, rtp_payload_start, rtp_payload, 0, rtp_payload.Length); // copy payload temporary_rtp_payloads.Add(rtp_payload); if (rtp_marker == 1) { // Process the RTP frame Process_RTP_Frame(temporary_rtp_payloads); temporary_rtp_payloads.Clear(); } }
private void Rtsp_client_MessageReceived(object sender, Rtsp.RtspChunkEventArgs e) { Console.WriteLine("Message Received " + e.ToString()); Rtsp.Messages.RtspResponse message = e.Message as Rtsp.Messages.RtspResponse; Console.WriteLine("Received " + message.OriginalRequest.ToString()); if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestOptions) { // send the Describe Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); rtsp_client.SendMessage(describe_message); } if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestDescribe) { // Got a reply for DESCRIBE // Examine the SDP Console.Write(System.Text.Encoding.UTF8.GetString(message.Data)); Rtsp.Sdp.SdpFile sdp_data; using (StreamReader sdp_stream = new StreamReader(new MemoryStream(message.Data))) { sdp_data = Rtsp.Sdp.SdpFile.Read(sdp_stream); } // Process each 'Media' Attribute in the SDP. // If the attribute is for Video, then carry out a SETUP and a PLAY for (int x = 0; x < sdp_data.Medias.Count; x++) { if (sdp_data.Medias[x].GetMediaType() == Rtsp.Sdp.Media.MediaType.video) { // seach the atributes for control, fmtp and rtpmap String control = ""; // the "track" or "stream id" String fmtp = ""; // holds SPS and PPS String rtpmap = ""; // holds Payload format, eg 96 often used with H264 as first dynamic payload value foreach (Rtsp.Sdp.Attribut attrib in sdp_data.Medias[x].Attributs) { if (attrib.Key.Equals("control")) { control = attrib.Value; } if (attrib.Key.Equals("fmtp")) { fmtp = attrib.Value; } if (attrib.Key.Equals("rtpmap")) { rtpmap = attrib.Value; } } String[] split_rtpmap = rtpmap.Split(' '); video_payload = 0; bool result = Int32.TryParse(split_rtpmap[0], out video_payload); // Transport: RTP/AVP;unicast;client_port=8000-8001 // Transport: RTP/AVP/TCP;interleaved=0-1 Rtsp.Messages.RtspRequest setup_message = new Rtsp.Messages.RtspRequestSetup(); setup_message.RtspUri = new Uri(url + "/" + control); setup_message.AddHeader("Transport: RTP/AVP/TCP;interleaved=0"); rtsp_client.SendMessage(setup_message); } } } if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestSetup) { // Got Reply to SETUP Console.WriteLine("Got reply from Setup. Session is " + message.Session); String session = message.Session; // Session value used with Play, Pause, Teardown Rtsp.Messages.RtspRequest play_message = new Rtsp.Messages.RtspRequestPlay(); play_message.RtspUri = new Uri(url); play_message.Session = session; // play_message.Timeout = 65; rtsp_client.SendMessage(play_message); } if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestPlay) { // Got Reply to PLAU Console.WriteLine("Got reply from Play " + message.Command); } }
// RTSP Messages are OPTIONS, DESCRIBE, SETUP, PLAY etc private void Rtsp_MessageReceived(object sender, Rtsp.RtspChunkEventArgs e) { Rtsp.Messages.RtspResponse message = e.Message as Rtsp.Messages.RtspResponse; Console.WriteLine("Received " + message.OriginalRequest.ToString()); // If we get a reply to OPTIONS and CSEQ is 1 (which was our first command), then send the DESCRIBE // If we fer a reply to OPTIONS and CSEQ is not 1, it must have been a keepalive command if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestOptions) { if (message.CSeq == 1) { // Start a Timer to send an OPTIONS command (for keepalive) every 20 seconds keepalive_timer = new System.Timers.Timer(); keepalive_timer.Elapsed += Timer_Elapsed; keepalive_timer.Interval = 20 * 1000; keepalive_timer.Enabled = true; // send the Describe Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); rtsp_client.SendMessage(describe_message); } else { // do nothing } } // If we get a reply to DESCRIBE (which was our second command), then prosess SDP and send the SETUP if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestDescribe) { // Got a reply for DESCRIBE // Examine the SDP Console.Write(System.Text.Encoding.UTF8.GetString(message.Data)); Rtsp.Sdp.SdpFile sdp_data; using (StreamReader sdp_stream = new StreamReader(new MemoryStream(message.Data))) { sdp_data = Rtsp.Sdp.SdpFile.Read(sdp_stream); } // Process each 'Media' Attribute in the SDP (each sub-stream) // If the attribute is for Video, then carry out a SETUP and a PLAY // Only do this for the first Video attribute in case there is more than one in the SDP for (int x = 0; x < sdp_data.Medias.Count; x++) { if (sdp_data.Medias[x].GetMediaType() == Rtsp.Sdp.Media.MediaType.video) { // We only want the first video sub-stream if (video_payload == -1) { // seach the atributes for control, fmtp and rtpmap String control = ""; // the "track" or "stream id" Rtsp.Sdp.AttributFmtp fmtp = null; // holds SPS and PPS in base64 Rtsp.Sdp.AttributRtpMap rtpmap = null; // holds Payload format, eg 96 often used with H264 as first dynamic payload value foreach (Rtsp.Sdp.Attribut attrib in sdp_data.Medias[x].Attributs) { if (attrib.Key.Equals("control")) { control = attrib.Value; } if (attrib.Key.Equals("fmtp")) { fmtp = attrib as Rtsp.Sdp.AttributFmtp; } if (attrib.Key.Equals("rtpmap")) { rtpmap = attrib as Rtsp.Sdp.AttributRtpMap; } } // Split the fmtp to get the sprop-parameter-sets which hold the SPS and PPS in base64 if (fmtp != null) { var param = Rtsp.Sdp.H264Parameters.Parse(fmtp.FormatParameter); var sps_pps = param.SpropParameterSets; if (sps_pps.Count > 0) { video_sps = sps_pps[0]; } if (sps_pps.Count > 1) { video_pps = sps_pps[1]; } Output_NAL(sps_pps); // output SPS and PPS } // Split the rtpmap to get the Payload Type video_payload = 0; if (rtpmap != null) { video_payload = rtpmap.PayloadNumber; } Rtsp.Messages.RtspRequestSetup setup_message = new Rtsp.Messages.RtspRequestSetup(); setup_message.RtspUri = new Uri(url + "/" + control); RtspTransport transport = null; if (rtp_transport == RTP_TRANSPORT.TCP) { // Server interleaves the RTP packets over the RTSP connection // Example for TCP mode (RTP over RTSP) Transport: RTP/AVP/TCP;interleaved=0-1 video_data_channel = 0; // Used in DataReceived event handler video_rtcp_channel = 1; // Used in DataReceived event handler transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(video_data_channel, video_rtcp_channel), // Channel 0 for video. Channel 1 for RTCP status reports }; } if (rtp_transport == RTP_TRANSPORT.UDP) { // Server sends the RTP packets to a Pair of UDP Ports (one for data, one for rtcp control messages) // Example for UDP mode Transport: RTP/AVP;unicast;client_port=8000-8001 video_data_channel = udp_pair.data_port; // Used in DataReceived event handler video_rtcp_channel = udp_pair.control_port; // Used in DataReceived event handler transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = false, ClientPort = new PortCouple(video_data_channel, video_rtcp_channel), // a Channel for video. a Channel for RTCP status reports }; } if (rtp_transport == RTP_TRANSPORT.MULTICAST) { // Server sends the RTP packets to a Pair of UDP ports (one for data, one for rtcp control messages) // using Multicast Address and Ports that are in the reply to the SETUP message // Example for MULTICAST mode Transport: RTP/AVP;multicast video_data_channel = 0; // we get this information in the SETUP message reply video_rtcp_channel = 0; // we get this information in the SETUP message reply transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = true }; } setup_message.AddTransport(transport); rtsp_client.SendMessage(setup_message); } } } } // If we get a reply to SETUP (which was our third command), then process then send PLAY if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestSetup) { // Got Reply to SETUP Console.WriteLine("Got reply from Setup. Session is " + message.Session); session = message.Session; // Session value used with Play, Pause, Teardown // Check the Transport header if (message.Headers.ContainsKey(RtspHeaderNames.Transport)) { RtspTransport transport = RtspTransport.Parse(message.Headers[RtspHeaderNames.Transport]); // Check if Transport header includes Multicast if (transport.IsMulticast) { String multicast_address = transport.Destination; video_data_channel = transport.Port.First; video_rtcp_channel = transport.Port.Second; // Create the Pair of UDP Sockets in Multicast mode udp_pair = new UDPSocket(multicast_address, video_data_channel, multicast_address, video_rtcp_channel); udp_pair.DataReceived += Rtp_DataReceived; udp_pair.Start(); } } Rtsp.Messages.RtspRequest play_message = new Rtsp.Messages.RtspRequestPlay(); play_message.RtspUri = new Uri(url); play_message.Session = session; rtsp_client.SendMessage(play_message); } // If we get a reply to PLAY (which was our fourth command), then we should have video being received if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestPlay) { // Got Reply to PLAY Console.WriteLine("Got reply from Play " + message.Command); } }
int rtp_count = 0; // used for statistics // RTP packet (or RTCP packet) has been received. public void Rtp_DataReceived(object sender, Rtsp.RtspChunkEventArgs e) { Rtsp.Messages.RtspData data_received = e.Message as Rtsp.Messages.RtspData; // Check which channel the Data was received on. // eg the Video Channel, the Video Control Channel (RTCP) // In the future would also check the Audio Channel and Audio Control Channel if (data_received.Channel == video_rtcp_channel) { Console.WriteLine("Received a RTCP message on channel " + data_received.Channel); return; } if (data_received.Channel == video_data_channel) { // Received some Video Data on the correct channel. // RTP Packet Header // 0 - Version, P, X, CC, M, PT and Sequence Number //32 - Timestamp //64 - SSRC //96 - CSRCs (optional) //nn - Extension ID and Length //nn - Extension header int rtp_version = (e.Message.Data[0] >> 6); int rtp_padding = (e.Message.Data[0] >> 5) & 0x01; int rtp_extension = (e.Message.Data[0] >> 4) & 0x01; int rtp_csrc_count = (e.Message.Data[0] >> 0) & 0x0F; int rtp_marker = (e.Message.Data[1] >> 7) & 0x01; int rtp_payload_type = (e.Message.Data[1] >> 0) & 0x7F; uint rtp_sequence_number = ((uint)e.Message.Data[2] << 8) + (uint)(e.Message.Data[3]); uint rtp_timestamp = ((uint)e.Message.Data[4] << 24) + (uint)(e.Message.Data[5] << 16) + (uint)(e.Message.Data[6] << 8) + (uint)(e.Message.Data[7]); uint rtp_ssrc = ((uint)e.Message.Data[8] << 24) + (uint)(e.Message.Data[9] << 16) + (uint)(e.Message.Data[10] << 8) + (uint)(e.Message.Data[11]); int rtp_payload_start = 4 // V,P,M,SEQ + 4 // time stamp + 4 // ssrc + (4 * rtp_csrc_count); // zero or more csrcs uint rtp_extension_id = 0; uint rtp_extension_size = 0; if (rtp_extension == 1) { rtp_extension_id = ((uint)e.Message.Data[rtp_payload_start + 0] << 8) + (uint)(e.Message.Data[rtp_payload_start + 1] << 0); rtp_extension_size = ((uint)e.Message.Data[rtp_payload_start + 2] << 8) + (uint)(e.Message.Data[rtp_payload_start + 3] << 0) * 4; // units of extension_size is 4-bytes rtp_payload_start += 4 + (int)rtp_extension_size; // extension header and extension payload } Console.WriteLine("RTP Data" + " V=" + rtp_version + " P=" + rtp_padding + " X=" + rtp_extension + " CC=" + rtp_csrc_count + " M=" + rtp_marker + " PT=" + rtp_payload_type + " Seq=" + rtp_sequence_number + " Time (MS)=" + rtp_timestamp / 90 // convert from 90kHZ clock to ms + " SSRC=" + rtp_ssrc + " Size=" + e.Message.Data.Length); String msg = "RTP Data " + rtp_count++ + " V=" + rtp_version + " P=" + rtp_padding + " X=" + rtp_extension + " CC=" + rtp_csrc_count + " M=" + rtp_marker + " PT=" + rtp_payload_type // + " Seq=" + rtp_sequence_number // + " Time=" + rtp_timestamp // + " SSRC=" + rtp_ssrc + " Size=" + e.Message.Data.Length; fs2.WriteLine(msg); fs2.Flush(); // Check the payload type in the RTP packet matches the Payload Type value from the SDP if (rtp_payload_type != video_payload) { Console.WriteLine("Ignoring this RTP payload"); return; // ignore this data } // If rtp_marker is '1' then this is the final transmission for this packet. // If rtp_marker is '0' we need to accumulate data with the same timestamp // ToDo - Check Timestamp // ToDo - Could avoid a copy if there is only one RTP frame for the data (temp list is zero) // Add the RTP packet to the tempoary_rtp list byte[] rtp_payload = new byte[e.Message.Data.Length - rtp_payload_start]; // payload with RTP header removed System.Array.Copy(e.Message.Data, rtp_payload_start, rtp_payload, 0, rtp_payload.Length); // copy payload temporary_rtp_payloads.Add(rtp_payload); if (rtp_marker == 1) { // End Marker is set. Process the RTP frame Process_RTP_Frame(temporary_rtp_payloads); temporary_rtp_payloads.Clear(); } } }
int rtp_count = 0; // used for statistics // RTP packet (or RTCP packet) has been received. public void Rtp_DataReceived(object sender, Rtsp.RtspChunkEventArgs e) { Rtsp.Messages.RtspData data_received = e.Message as Rtsp.Messages.RtspData; // Check which channel the Data was received on. // eg the Video Channel, the Video Control Channel (RTCP) // In the future would also check the Audio Channel and Audio Control Channel if (data_received.Channel == video_rtcp_channel) { Console.WriteLine("Received a RTCP message on channel " + data_received.Channel); return; } if (data_received.Channel == video_data_channel) { // Received some Video Data on the correct channel. // RTP Packet Header // 0 - Version, P, X, CC, M, PT and Sequence Number //32 - Timestamp //64 - SSRC //96 - CSRCs (optional) //nn - Extension ID and Length //nn - Extension header int rtp_version = (e.Message.Data[0] >> 6); int rtp_padding = (e.Message.Data[0] >> 5) & 0x01; int rtp_extension = (e.Message.Data[0] >> 4) & 0x01; int rtp_csrc_count = (e.Message.Data[0] >> 0) & 0x0F; int rtp_marker = (e.Message.Data[1] >> 7) & 0x01; int rtp_payload_type = (e.Message.Data[1] >> 0) & 0x7F; uint rtp_sequence_number = ((uint)e.Message.Data[2] << 8) + (uint)(e.Message.Data[3]); uint rtp_timestamp = ((uint)e.Message.Data[4] << 24) + (uint)(e.Message.Data[5] << 16) + (uint)(e.Message.Data[6] << 8) + (uint)(e.Message.Data[7]); uint rtp_ssrc = ((uint)e.Message.Data[8] << 24) + (uint)(e.Message.Data[9] << 16) + (uint)(e.Message.Data[10] << 8) + (uint)(e.Message.Data[11]); int rtp_payload_start = 4 // V,P,M,SEQ + 4 // time stamp + 4 // ssrc + (4 * rtp_csrc_count); // zero or more csrcs uint rtp_extension_id = 0; uint rtp_extension_size = 0; if (rtp_extension == 1) { rtp_extension_id = ((uint)e.Message.Data[rtp_payload_start + 0] << 8) + (uint)(e.Message.Data[rtp_payload_start + 1] << 0); rtp_extension_size = ((uint)e.Message.Data[rtp_payload_start + 2] << 8) + (uint)(e.Message.Data[rtp_payload_start + 3] << 0) * 4; // units of extension_size is 4-bytes rtp_payload_start += 4 + (int)rtp_extension_size; // extension header and extension payload } Console.WriteLine("RTP Data" + " V=" + rtp_version + " P=" + rtp_padding + " X=" + rtp_extension + " CC=" + rtp_csrc_count + " M=" + rtp_marker + " PT=" + rtp_payload_type + " Seq=" + rtp_sequence_number + " Time (MS)=" + rtp_timestamp / 90 // convert from 90kHZ clock to ms + " SSRC=" + rtp_ssrc + " Size=" + e.Message.Data.Length); String msg = "RTP Data " + rtp_count++ + " V=" + rtp_version + " P=" + rtp_padding + " X=" + rtp_extension + " CC=" + rtp_csrc_count + " M=" + rtp_marker + " PT=" + rtp_payload_type // + " Seq=" + rtp_sequence_number // + " Time=" + rtp_timestamp // + " SSRC=" + rtp_ssrc + " Size=" + e.Message.Data.Length; if (fs2 != null) { fs2.WriteLine(msg); } if (fs2 != null) { fs2.Flush(); } // Check the payload type in the RTP packet matches the Payload Type value from the SDP if (rtp_payload_type != video_payload) { Console.WriteLine("Ignoring this RTP payload"); return; // ignore this data } if (rtp_payload_type >= 96 && rtp_payload_type <= 127 && video_codec.Equals("H264")) { // H264 RTP Packet // If rtp_marker is '1' then this is the final transmission for this packet. // If rtp_marker is '0' we need to accumulate data with the same timestamp // ToDo - Check Timestamp // Add the RTP packet to the tempoary_rtp list until we have a complete 'Frame' byte[] rtp_payload = new byte[e.Message.Data.Length - rtp_payload_start]; // payload with RTP header removed System.Array.Copy(e.Message.Data, rtp_payload_start, rtp_payload, 0, rtp_payload.Length); // copy payload List <byte[]> nal_units = h264Payload.Process_H264_RTP_Packet(rtp_payload, rtp_marker); // this will cache the Packets until there is a Frame if (nal_units == null) { // we have not passed in enough RTP packets to make a Frame of video } else { // we have a frame of NAL Units. Write them to the file Output_NAL(nal_units); } } else if (rtp_payload_type == 26) { Console.WriteLine("No parser for JPEG RTP packets"); } else { Console.WriteLine("No parser for this RTP payload"); } } }
// RTSP Messages are OPTIONS, DESCRIBE, SETUP, PLAY etc private void Rtsp_MessageReceived(object sender, Rtsp.RtspChunkEventArgs e) { Rtsp.Messages.RtspResponse message = e.Message as Rtsp.Messages.RtspResponse; Console.WriteLine("Received " + message.OriginalRequest.ToString()); // Check if the Message has an Authenticate header. If so we update the 'realm' and 'nonce' if (message.Headers.ContainsKey(RtspHeaderNames.WWWAuthenticate)) { String www_authenticate = message.Headers[RtspHeaderNames.WWWAuthenticate]; var authHeader = AuthenticationHeaderValue.Parse(www_authenticate); authorizationScheme = authHeader.Scheme; var authParams = authHeader.Parameter .Split(',') .Select(p => p.Split('=')) .ToDictionary(p => p[0].Trim(' ', '"'), p => p[1].Trim(' ', '"')); // For now, assuming auth scheme is either Basic or Digest realm = authParams["realm"]; if (authorizationScheme == "Digest") { nonce = authParams["nonce"]; } Console.WriteLine("WWW Authorize parsed for " + realm + " " + nonce ?? "<NULL>"); } // If we get a reply to OPTIONS and CSEQ is 1 (which was our first command), then send the DESCRIBE // If we fer a reply to OPTIONS and CSEQ is not 1, it must have been a keepalive command if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestOptions) { if (message.CSeq == 1) { // Start a Timer to send an OPTIONS command (for keepalive) every 20 seconds keepalive_timer = new System.Timers.Timer(); keepalive_timer.Elapsed += Timer_Elapsed; keepalive_timer.Interval = 20 * 1000; keepalive_timer.Enabled = true; // send the DESCRIBE. First time around we have no WWW-Authorise Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); rtsp_client.SendMessage(describe_message); } else { // do nothing } } // If we get a reply to DESCRIBE (which was our second command), then prosess SDP and send the SETUP if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestDescribe) { // Got a reply for DESCRIBE // First time we send DESCRIBE we will not have the authorization Nonce so we // handle the Unauthorized 401 error here and send a new DESCRIBE message if (message.IsOk == false) { Console.WriteLine("Got Error in DESCRIBE Reply " + message.ReturnCode + " " + message.ReturnMessage); if (message.ReturnCode == 401 && (message.OriginalRequest.Headers.ContainsKey(RtspHeaderNames.Authorization) == false)) { // Error 401 - Unauthorized, but the request did not use Authorizarion. if (username == null || password == null) { // we do nothave a username or password. Abort return; } // Send a new DESCRIBE with authorization String authorization = GenerateAuthorization("DESCRIBE"); Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); if (authorization != null) { describe_message.Headers.Add(RtspHeaderNames.Authorization, authorization); } rtsp_client.SendMessage(describe_message); return; } else if (message.ReturnCode == 401 && (message.OriginalRequest.Headers.ContainsKey(RtspHeaderNames.Authorization) == true)) { // Authorization failed return; } else { // some other error return; } } // Examine the SDP Console.Write(System.Text.Encoding.UTF8.GetString(message.Data)); Rtsp.Sdp.SdpFile sdp_data; using (StreamReader sdp_stream = new StreamReader(new MemoryStream(message.Data))) { sdp_data = Rtsp.Sdp.SdpFile.Read(sdp_stream); } // Process each 'Media' Attribute in the SDP (each sub-stream) // If the attribute is for Video, then carry out a SETUP and a PLAY // Only do this for the first Video attribute in case there is more than one in the SDP for (int x = 0; x < sdp_data.Medias.Count; x++) { if (sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.video) { // We only want the first video sub-stream if (video_payload == -1) { video_payload = sdp_data.Medias[x].PayloadType; // search the attributes for control, fmtp and rtpmap String control = ""; // the "track" or "stream id" Rtsp.Sdp.AttributFmtp fmtp = null; // holds SPS and PPS in base64 (h264) Rtsp.Sdp.AttributRtpMap rtpmap = null; // custom payload (>=96) details foreach (Rtsp.Sdp.Attribut attrib in sdp_data.Medias[x].Attributs) { if (attrib.Key.Equals("control")) { String sdp_control = attrib.Value; if (sdp_control.ToLower().StartsWith("rtsp://")) { control = sdp_control; //absolute path } else { control = url + "/" + sdp_control; // relative path } } if (attrib.Key.Equals("fmtp")) { fmtp = attrib as Rtsp.Sdp.AttributFmtp; } if (attrib.Key.Equals("rtpmap")) { rtpmap = attrib as Rtsp.Sdp.AttributRtpMap; } } // If the rtpmap contains H264 then split the fmtp to get the sprop-parameter-sets which hold the SPS and PPS in base64 if (rtpmap != null && rtpmap.Value.Contains("H264") && fmtp != null) { video_codec = "H264"; var param = Rtsp.Sdp.H264Parameters.Parse(fmtp.FormatParameter); var sps_pps = param.SpropParameterSets; if (sps_pps.Count() >= 2) { byte[] sps = sps_pps[0]; byte[] pps = sps_pps[1]; Output_SPS_PPS(sps, pps); // output SPS and PPS } } RtspTransport transport = null; if (rtp_transport == RTP_TRANSPORT.TCP) { // Server interleaves the RTP packets over the RTSP connection // Example for TCP mode (RTP over RTSP) Transport: RTP/AVP/TCP;interleaved=0-1 video_data_channel = 0; // Used in DataReceived event handler video_rtcp_channel = 1; // Used in DataReceived event handler transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(video_data_channel, video_rtcp_channel), // Channel 0 for video. Channel 1 for RTCP status reports }; } if (rtp_transport == RTP_TRANSPORT.UDP) { // Server sends the RTP packets to a Pair of UDP Ports (one for data, one for rtcp control messages) // Example for UDP mode Transport: RTP/AVP;unicast;client_port=8000-8001 video_data_channel = udp_pair.data_port; // Used in DataReceived event handler video_rtcp_channel = udp_pair.control_port; // Used in DataReceived event handler transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = false, ClientPort = new PortCouple(video_data_channel, video_rtcp_channel), // a Channel for video. a Channel for RTCP status reports }; } if (rtp_transport == RTP_TRANSPORT.MULTICAST) { // Server sends the RTP packets to a Pair of UDP ports (one for data, one for rtcp control messages) // using Multicast Address and Ports that are in the reply to the SETUP message // Example for MULTICAST mode Transport: RTP/AVP;multicast video_data_channel = 0; // we get this information in the SETUP message reply video_rtcp_channel = 0; // we get this information in the SETUP message reply transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = true }; } // Add authorization (if there is a username and password) String digest_authorization = GenerateAuthorization("SETUP"); // Send SETUP Rtsp.Messages.RtspRequestSetup setup_message = new Rtsp.Messages.RtspRequestSetup(); setup_message.RtspUri = new Uri(control); setup_message.AddTransport(transport); if (digest_authorization != null) { setup_message.Headers.Add("Authorization", digest_authorization); } rtsp_client.SendMessage(setup_message); } } } } // If we get a reply to SETUP (which was our third command), then process and then send PLAY if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestSetup) { // Got Reply to SETUP if (message.IsOk == false) { Console.WriteLine("Got Error in SETUP Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } Console.WriteLine("Got reply from Setup. Session is " + message.Session); session = message.Session; // Session value used with Play, Pause, Teardown // Check the Transport header if (message.Headers.ContainsKey(RtspHeaderNames.Transport)) { RtspTransport transport = RtspTransport.Parse(message.Headers[RtspHeaderNames.Transport]); // Check if Transport header includes Multicast if (transport.IsMulticast) { String multicast_address = transport.Destination; video_data_channel = transport.Port.First; video_rtcp_channel = transport.Port.Second; // Create the Pair of UDP Sockets in Multicast mode udp_pair = new UDPSocket(multicast_address, video_data_channel, multicast_address, video_rtcp_channel); udp_pair.DataReceived += Rtp_DataReceived; udp_pair.Start(); } } // Send PLAY Rtsp.Messages.RtspRequest play_message = new Rtsp.Messages.RtspRequestPlay(); play_message.RtspUri = new Uri(url); play_message.Session = session; rtsp_client.SendMessage(play_message); } // If we get a reply to PLAY (which was our fourth command), then we should have video being received if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestPlay) { // Got Reply to PLAY if (message.IsOk == false) { Console.WriteLine("Got Error in PLAY Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } Console.WriteLine("Got reply from Play " + message.Command); } }