/// <summary> /// Raises the <see cref="E:MessageReceived"/> event. /// </summary> /// <param name="e">The <see cref="Mictlanix.DotNet.Rtsp.RtspChunkEventArgs"/> instance containing the event data.</param> protected void OnMessageReceived(RtspChunkEventArgs e) { EventHandler <RtspChunkEventArgs> handler = MessageReceived; if (handler != null) { handler(this, e); } }
/// <summary> /// Raises the <see cref="E:DataReceived"/> event. /// </summary> /// <param name="rtspChunkEventArgs">The <see cref="Mictlanix.DotNet.Rtsp.RtspChunkEventArgs"/> instance containing the event data.</param> protected void OnDataReceived(RtspChunkEventArgs rtspChunkEventArgs) { EventHandler <RtspChunkEventArgs> handler = DataReceived; if (handler != null) { handler(this, rtspChunkEventArgs); } }
// RTSP Messages are OPTIONS, DESCRIBE, SETUP, PLAY etc void Rtsp_MessageReceived(object sender, RtspChunkEventArgs e) { var message = e.Message as RtspResponse; //logger.Debug ("Received " + message.OriginalRequest.Method); // 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) { logger.Warn("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 } } } //logger.Debug ("WWW Authorize parsed for " + auth_type + " " + realm + " " + nonce); } var 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 RtspRequestOptions) { string public_methods = message.Headers [RtspHeaderNames.Public]; SupportedMethods.Clear(); foreach (string method in public_methods.Split(',')) { SupportedMethods.Add(method.Trim()); } 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 = 5 * 1000; keepalive_timer.Enabled = true; // Send DESCRIBE var describe_message = new RtspRequestDescribe(); describe_message.RtspUri = new Uri(Url); describe_message.AddAccept("application/sdp"); if (auth_type != null) { AddAuthorization(describe_message, Username, Password, auth_type, realm, nonce, Url); } rtsp_client.SendMessage(describe_message); } } // 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 Mictlanix.DotNet.Rtsp.Messages.RtspRequestDescribe) { // Got a reply for DESCRIBE if (!message.IsOk) { logger.Warn("Got Error in DESCRIBE Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } // Examine the SDP //logger.Debug (Encoding.UTF8.GetString (message.Data)); Sdp.SdpFile sdp_data; using (var sdp_stream = new StreamReader(new MemoryStream(message.Data))) { sdp_data = 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 video = (sdp_data.Medias [x].MediaType == Mictlanix.DotNet.Rtsp.Sdp.Media.MediaTypes.video); if (video && video_payload != -1) { continue; // have already matched an video payload } if (video) { // search the attributes for control, rtpmap and fmtp // (fmtp only applies to video) string control = ""; // the "track" or "stream id" Sdp.FmtpAttribute fmtp = null; // holds SPS and PPS in base64 (h264 video) foreach (var attrib in sdp_data.Medias[x].Attributs) { if (attrib.Key.Equals("control")) { string sdp_control = attrib.Value; if (sdp_control.ToLower().StartsWith("rtsp://", StringComparison.Ordinal)) { control = sdp_control; //absolute path } else if (message.Headers.ContainsKey(RtspHeaderNames.ContentBase)) { control = message.Headers [RtspHeaderNames.ContentBase] + sdp_control; // relative path } else { control = Url + "/" + sdp_control; // relative path } } if (attrib.Key.Equals("fmtp")) { fmtp = attrib as Sdp.FmtpAttribute; } if (attrib.Key.Equals("rtpmap")) { var rtpmap = attrib as Sdp.RtpMapAttribute; // Check if the Codec Used (EncodingName) is one we support string [] valid_video_codecs = { "H264" }; 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 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 = 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]; ParameterSetsReceived?.Invoke(sps, pps); } } // Send the SETUP RTSP command if we have a matching Payload Decoder if (video && video_payload == -1) { continue; } // Server interleaves the RTP packets over the RTSP connection // TCP mode (RTP over RTSP) Transport: RTP/AVP/TCP;interleaved=0-1 video_data_channel = 0; video_rtcp_channel = 1; var transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(video_data_channel, video_rtcp_channel), // Eg Channel 0 for video. Channel 1 for RTCP status reports }; // Send SETUP var setup_message = new 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); } 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 RtspRequestSetup) { // Got Reply to SETUP if (!message.IsOk) { logger.Warn("Got Error in SETUP Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } //logger.Debug ("Got reply from SETUP Session=" + message.Session); RtspSession = message.Session; // Session value used with Play, Pause, Teardown // Send PLAY RtspRequest play_message = new RtspRequestPlay(); play_message.RtspUri = new Uri(Url); play_message.Session = RtspSession; 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 RtspRequestPlay) { // Got Reply to PLAY if (!message.IsOk) { logger.Warn("Got Error in PLAY Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } //logger.Debug ("Got reply from PLAY " + message.Command); } }
// int rtp_count = 0; // used for statistics // RTP packet (or RTCP packet) has been received. void Rtp_DataReceived(object sender, RtspChunkEventArgs e) { var data_received = e.Message as RtspData; // Check which channel the Data was received on. // eg the Video Channel or the Video Control Channel (RTCP) if (data_received.Channel == video_rtcp_channel) { //logger.Debug ("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 //logger.Debug ("RTCP Data. PacketType=" + rtcp_packet_type + " SSRC=" + rtcp_ssrc); if (rtcp_packet_type == 200) { // Send a Receiver Report try { SendEmptyReceiverReport(); } catch { logger.Warn("Error writing RTCP packet"); } } packetIndex = packetIndex + ((rtcp_length + 1) * 4); } 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 } //logger.Debug ("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 (rtp_payload_type != video_payload) { logger.Warn("Ignoring this Video 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 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 // we have not passed in enough RTP packets to make a Frame of video // we have a frame of NAL Units. Write them to the file if (nal_units != null) { FrameReceived?.Invoke(nal_units); } } else { logger.Warn("No parser for RTP payload " + rtp_payload_type); } } }