Exemple #1
0
    void video_source_ReceivedYUVFrame(uint timestamp_ms, int width, int height, byte[] yuv_data)
    {
        // Check if there are any clients. Only run the encoding if someone is connected
        // Could exand this to check if someone is connected and in PLAY mode
        int current_rtp_count = rtp_list.Count;

        if (current_rtp_count == 0)
        {
            return;
        }

        // Take the YUV image and encode it into a H264 NAL
        // This returns a NAL with no headers (no 00 00 00 01 header and no 32 bit sizes)
        Console.WriteLine("Compressing video at time(ms) " + timestamp_ms + "    " + current_rtp_count + " RTSP clients connected");
        byte[] raw_nal = h264_encoder.CompressFrame(yuv_data);

        UInt32 ts = timestamp_ms * 90; // 90kHz clock

        // The H264 Payload could be sent as one large RTP packet (assuming the receiver can handle it)
        // or as a Fragmented Data, split over several RTP packets with the same Timestamp.
        bool fragmenting = false;

        if (raw_nal.Length > 1400)
        {
            fragmenting = true;
        }

        // Build a list of 1 or more RTP packets
        List <byte[]> rtp_packets = new List <byte[]>();

        if (fragmenting == false)
        {
            // Put the whole NAL into one RTP packet.
            // Note some receivers will have maximum buffers and be unable to handle large RTP packets.
            // Also with RTP over RTSP there is a limit of 65535 bytes for the RTP packet.

            byte[] rtp_packet = new byte[12 + raw_nal.Length]; // 12 is header size when there are no CSRCs or extensions
            // Create an single RTP fragment

            // RTP Packet Header
            // 0 - Version, P, X, CC, M, PT and Sequence Number
            //32 - Timestamp. H264 uses a 90kHz clock
            //64 - SSRC
            //96 - CSRCs (optional)
            //nn - Extension ID and Length
            //nn - Extension header

            int rtp_version      = 2;
            int rtp_padding      = 0;
            int rtp_extension    = 0;
            int rtp_csrc_count   = 0;
            int rtp_marker       = 1;
            int rtp_payload_type = 96;

            RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);

            UInt32 empty_sequence_id = 0;
            RTPPacketUtil.WriteSequenceNumber(rtp_packet, empty_sequence_id);

            Console.WriteLine("adjusted TS at 90khz=" + ts);
            RTPPacketUtil.WriteTS(rtp_packet, ts);

            UInt32 empty_ssrc = 0;
            RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);

            // Now append the raw NAL
            System.Array.Copy(raw_nal, 0, rtp_packet, 12, raw_nal.Length);

            rtp_packets.Add(rtp_packet);
        }
        else
        {
            int data_remaining = raw_nal.Length;
            int nal_pointer    = 0;
            int start_bit      = 1;
            int end_bit        = 0;

            // consume first byte of the raw_nal. It is used in the FU header
            byte first_byte = raw_nal[0];
            nal_pointer++;
            data_remaining--;

            while (data_remaining > 0)
            {
                int payload_size = Math.Min(1400, data_remaining);
                if (data_remaining - payload_size == 0)
                {
                    end_bit = 1;
                }

                byte[] rtp_packet = new byte[12 + 2 + payload_size]; // 12 is header size. 2 bytes for FU-A header. Then payload

                // RTP Packet Header
                // 0 - Version, P, X, CC, M, PT and Sequence Number
                //32 - Timestamp. H264 uses a 90kHz clock
                //64 - SSRC
                //96 - CSRCs (optional)
                //nn - Extension ID and Length
                //nn - Extension header

                int rtp_version      = 2;
                int rtp_padding      = 0;
                int rtp_extension    = 0;
                int rtp_csrc_count   = 0;
                int rtp_marker       = (end_bit == 1 ? 1 : 0); // Marker set to 1 on last packet
                int rtp_payload_type = 96;

                RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);

                UInt32 empty_sequence_id = 0;
                RTPPacketUtil.WriteSequenceNumber(rtp_packet, empty_sequence_id);

                Console.WriteLine("adjusted TS at 90khz=" + ts);
                RTPPacketUtil.WriteTS(rtp_packet, ts);

                UInt32 empty_ssrc = 0;
                RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);

                // Now append the Fragmentation Header (with Start and End marker) and part of the raw_nal
                byte f_bit = 0;
                byte nri   = (byte)((first_byte >> 5) & 0x03); // Part of the 1st byte of the Raw NAL (NAL Reference ID)
                byte type  = 28;                               // FU-A Fragmentation

                rtp_packet[12] = (byte)((f_bit << 7) + (nri << 5) + type);
                rtp_packet[13] = (byte)((start_bit << 7) + (end_bit << 6) + (0 << 5) + (first_byte & 0x1F));

                System.Array.Copy(raw_nal, nal_pointer, rtp_packet, 14, payload_size);
                nal_pointer    = nal_pointer + payload_size;
                data_remaining = data_remaining - payload_size;

                rtp_packets.Add(rtp_packet);

                start_bit = 0;
            }
        }

        lock (rtp_list)
        {
            // Go through each RTSP session and output the NAL
            foreach (RTPSession session in rtp_list.ToArray()) // ToArray makes a temp copy of the list.
                                                               // This lets us delete items in the foreach
            {
                // Only process Sessions in Play Mode
                if (session.play == false)
                {
                    continue;
                }

                // There could be more than 1 RTP packet (if the data is fragmented)
                Boolean write_error = false;
                foreach (byte[] rtp_packet in rtp_packets)
                {
                    // Add the specific data for each transmission
                    RTPPacketUtil.WriteSequenceNumber(rtp_packet, session.sequence_number);
                    session.sequence_number++;

                    // Add the specific SSRC for each transmission
                    RTPPacketUtil.WriteSSRC(rtp_packet, session.ssrc);


                    // Send as RTP over RTSP
                    if (session.transport_reply.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.TCP)
                    {
                        int    video_channel = session.transport_reply.Interleaved.First; // second is for RTCP status messages)
                        object state         = new object();
                        try
                        {
                            // send the whole NAL. With RTP over RTSP we do not need to Fragment the NAL (as we do with UDP packets or Multicast)
                            //session.listener.BeginSendData(video_channel, rtp_packet, new AsyncCallback(session.listener.EndSendData), state);
                            session.listener.SendData(video_channel, rtp_packet);
                        }
                        catch
                        {
                            Console.WriteLine("Error writing to listener " + session.listener.RemoteAdress);
                            write_error = true;
                            break; // exit out of foreach loop
                        }
                    }
                    // TODO. Add UDP and Multicast
                }
                if (write_error)
                {
                    session.play = false;     // stop sending data
                    session.listener.Dispose();
                    rtp_list.Remove(session); // remove the session. It is dead
                    Console.WriteLine(rtp_list.Count + " remaining sessions open");
                }
            }
        }
    }
Exemple #2
0
    // The 'Camera' (YUV TestCard) has generated a YUV image.
    // If there are RTSP clients connected then Compress the Video Frame (with H264) and send it to the client
    void video_source_ReceivedYUVFrame(uint timestamp_ms, int width, int height, byte[] yuv_data)
    {
        DateTime now = DateTime.UtcNow;
        int      current_rtp_play_count = 0;
        int      current_rtp_count      = 0;
        int      timeout_in_seconds     = 70; // must have a RTSP message every 70 seconds or we will close the connection

        lock (rtsp_list) {
            current_rtp_count = rtsp_list.Count;
            foreach (RTSPConnection connection in rtsp_list.ToArray())   // Convert to Array to allow us to delete from rtsp_list
            // RTSP Timeout (for UDP)
            {
                if ((connection.video_client_transport != null) &&
                    (connection.video_client_transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP) &&
                    ((now - connection.time_since_last_rtsp_keepalive).TotalSeconds > timeout_in_seconds))
                {
                    connection.play = false;

                    Console.WriteLine("Removing session " + connection.video_session_id + " due to TIMEOUT");
                    connection.play = false; // stop sending data
                    if (connection.video_udp_pair != null)
                    {
                        connection.video_udp_pair.Stop();
                        connection.video_udp_pair = null;
                    }
                    connection.listener.Dispose();

                    rtsp_list.Remove(connection);
                    continue;
                }
                else if (connection.play)
                {
                    current_rtp_play_count++;
                }
            }
        }

        // Take the YUV image and encode it into a H264 NAL
        // This returns a NAL with no headers (no 00 00 00 01 header and no 32 bit sizes)
        Console.WriteLine(current_rtp_count + " RTSP clients connected. " + current_rtp_play_count + " RTSP clients in PLAY mode");

        if (current_rtp_play_count == 0)
        {
            return;
        }

        // Compress the video (YUV to H264)
        byte[] raw_nal = h264_encoder.CompressFrame(yuv_data);

        UInt32 rtp_timestamp = timestamp_ms * 90; // 90kHz clock

        // The H264 Payload could be sent as one large RTP packet (assuming the receiver can handle it)
        // or as a Fragmented Data, split over several RTP packets with the same Timestamp.
        bool fragmenting = false;
        int  packetMTU   = 65000; //1400;

        if (raw_nal.Length > packetMTU)
        {
            fragmenting = true;
        }

        // Build a list of 1 or more RTP packets
        List <byte[]> rtp_packets = new List <byte[]>();

        if (fragmenting == false)
        {
            // Put the whole NAL into one RTP packet.
            // Note some receivers will have maximum buffers and be unable to handle large RTP packets.
            // Also with RTP over RTSP there is a limit of 65535 bytes for the RTP packet.

            byte[] rtp_packet = new byte[12 + raw_nal.Length]; // 12 is header size when there are no CSRCs or extensions
            // Create an single RTP fragment

            // RTP Packet Header
            // 0 - Version, P, X, CC, M, PT and Sequence Number
            //32 - Timestamp. H264 uses a 90kHz clock
            //64 - SSRC
            //96 - CSRCs (optional)
            //nn - Extension ID and Length
            //nn - Extension header

            int rtp_version      = 2;
            int rtp_padding      = 0;
            int rtp_extension    = 0;
            int rtp_csrc_count   = 0;
            int rtp_marker       = 1;
            int rtp_payload_type = 96;

            RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);

            UInt32 empty_sequence_id = 0;
            RTPPacketUtil.WriteSequenceNumber(rtp_packet, empty_sequence_id);

            RTPPacketUtil.WriteTS(rtp_packet, rtp_timestamp);

            UInt32 empty_ssrc = 0;
            RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);

            // Now append the raw NAL
            System.Array.Copy(raw_nal, 0, rtp_packet, 12, raw_nal.Length);

            rtp_packets.Add(rtp_packet);
        }
        else
        {
            int data_remaining = raw_nal.Length;
            int nal_pointer    = 0;
            int start_bit      = 1;
            int end_bit        = 0;

            // consume first byte of the raw_nal. It is used in the FU header
            byte first_byte = raw_nal[0];
            nal_pointer++;
            data_remaining--;

            while (data_remaining > 0)
            {
                int payload_size = Math.Min(packetMTU, data_remaining);
                if (data_remaining - payload_size == 0)
                {
                    end_bit = 1;
                }

                byte[] rtp_packet = new byte[12 + 2 + payload_size]; // 12 is header size. 2 bytes for FU-A header. Then payload

                // RTP Packet Header
                // 0 - Version, P, X, CC, M, PT and Sequence Number
                //32 - Timestamp. H264 uses a 90kHz clock
                //64 - SSRC
                //96 - CSRCs (optional)
                //nn - Extension ID and Length
                //nn - Extension header

                int rtp_version      = 2;
                int rtp_padding      = 0;
                int rtp_extension    = 0;
                int rtp_csrc_count   = 0;
                int rtp_marker       = (end_bit == 1 ? 1 : 0); // Marker set to 1 on last packet
                int rtp_payload_type = 96;

                RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);

                UInt32 empty_sequence_id = 0;
                RTPPacketUtil.WriteSequenceNumber(rtp_packet, empty_sequence_id);

                RTPPacketUtil.WriteTS(rtp_packet, rtp_timestamp);

                UInt32 empty_ssrc = 0;
                RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);

                // Now append the Fragmentation Header (with Start and End marker) and part of the raw_nal
                byte f_bit = 0;
                byte nri   = (byte)((first_byte >> 5) & 0x03); // Part of the 1st byte of the Raw NAL (NAL Reference ID)
                byte type  = 28;                               // FU-A Fragmentation

                rtp_packet[12] = (byte)((f_bit << 7) + (nri << 5) + type);
                rtp_packet[13] = (byte)((start_bit << 7) + (end_bit << 6) + (0 << 5) + (first_byte & 0x1F));

                System.Array.Copy(raw_nal, nal_pointer, rtp_packet, 14, payload_size);
                nal_pointer    = nal_pointer + payload_size;
                data_remaining = data_remaining - payload_size;

                rtp_packets.Add(rtp_packet);

                start_bit = 0;
            }
        }

        lock (rtsp_list)
        {
            // Go through each RTSP connection and output the NAL on the Video Session
            foreach (RTSPConnection connection in rtsp_list.ToArray()) // ToArray makes a temp copy of the list.
            // This lets us delete items in the foreach
            // eg when there is Write Error
            {
                // Only process Sessions in Play Mode
                if (connection.play == false)
                {
                    continue;
                }

                String connection_type = "";
                if (connection.video_client_transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.TCP)
                {
                    connection_type = "TCP";
                }
                if (connection.video_client_transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP &&
                    connection.video_client_transport.IsMulticast == false)
                {
                    connection_type = "UDP";
                }
                if (connection.video_client_transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP &&
                    connection.video_client_transport.IsMulticast == true)
                {
                    connection_type = "Multicast";
                }
                Console.WriteLine("Sending video session " + connection.video_session_id + " " + connection_type + " Timestamp(ms)=" + timestamp_ms + ". RTP timestamp=" + rtp_timestamp + ". Sequence=" + connection.video_sequence_number);

                // There could be more than 1 RTP packet (if the data is fragmented)
                Boolean write_error = false;
                foreach (byte[] rtp_packet in rtp_packets)
                {
                    // Add the specific data for each transmission
                    RTPPacketUtil.WriteSequenceNumber(rtp_packet, connection.video_sequence_number);
                    connection.video_sequence_number++;

                    // Add the specific SSRC for each transmission
                    RTPPacketUtil.WriteSSRC(rtp_packet, connection.ssrc);


                    // Send as RTP over RTSP (Interleaved)
                    if (connection.video_transport_reply.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.TCP)
                    {
                        int    video_channel = connection.video_transport_reply.Interleaved.First; // second is for RTCP status messages)
                        object state         = new object();
                        try
                        {
                            // send the whole NAL. With RTP over RTSP we do not need to Fragment the NAL (as we do with UDP packets or Multicast)
                            //session.listener.BeginSendData(video_channel, rtp_packet, new AsyncCallback(session.listener.EndSendData), state);
                            connection.listener.SendData(video_channel, rtp_packet);
                        }
                        catch
                        {
                            Console.WriteLine("Error writing to listener " + connection.listener.RemoteAdress);
                            write_error = true;
                            break; // exit out of foreach loop
                        }
                    }

                    // Send as RTP over UDP
                    if (connection.video_transport_reply.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && connection.video_transport_reply.IsMulticast == false)
                    {
                        try
                        {
                            // send the whole NAL. ** We could fragment the RTP packet into smaller chuncks that fit within the MTU
                            // Send to the IP address of the Client
                            // Send to the UDP Port the Client gave us in the SETUP command
                            connection.video_udp_pair.Write_To_Data_Port(rtp_packet, connection.client_hostname, connection.video_client_transport.ClientPort.First);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine("UDP Write Exception " + e.ToString());
                            Console.WriteLine("Error writing to listener " + connection.listener.RemoteAdress);
                            write_error = true;
                            break; // exit out of foreach loop
                        }
                    }

                    // TODO. Add Multicast
                }
                if (write_error)
                {
                    Console.WriteLine("Removing session " + connection.video_session_id + " due to write error");
                    connection.play = false; // stop sending data
                    if (connection.video_udp_pair != null)
                    {
                        connection.video_udp_pair.Stop();
                        connection.video_udp_pair = null;
                    }
                    connection.listener.Dispose();
                    rtsp_list.Remove(connection); // remove the session. It is dead
                }
            }
        }
    }
Exemple #3
0
    private void Nvr_StreamFrameReceived(StreamFrame obj)
    {
        if (obj.StreamId != streamId)
        {
            return;
        }

        if (obj.FrameType != FrameType.Video)
        {
            return;
        }

        Console.WriteLine(obj.Time);
        Console.WriteLine((obj.Time & 0xFFFFFFFF));
        if (obj.KeyFrame)
        {
            nvr.StreamChange(streamId);
            // nvr.StreamStop(streamId);
            // nvr.StreamStart(0);
            // ++streamId;
        }

        //
        // }

        // The 'Camera' (YUV TestCard) has generated a YUV image.
        // If there are RTSP clients connected then Compress the Video Frame (with H264) and send it to the client
        // void video_source_ReceivedYUVFrame(uint timestamp_ms, int width, int height, byte[] yuv_data)
        // {
        DateTime now = DateTime.UtcNow;
        int      current_rtp_play_count = 0;
        int      current_rtp_count      = 0;
        int      timeout_in_seconds     = 70; // must have a RTSP message every 70 seconds or we will close the connection

        lock (rtsp_list)
        {
            current_rtp_count = rtsp_list.Count;
            foreach (RTSPConnection connection in rtsp_list.ToArray())
            { // Convert to Array to allow us to delete from rtsp_list
                // RTSP Timeout (clients receiving RTP video over the RTSP session
                // do not need to send a keepalive (so we check for Socket write errors)
                bool sending_rtp_via_tcp = false;
                if ((connection.video_client_transport != null) &&
                    (connection.video_client_transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.TCP))
                {
                    sending_rtp_via_tcp = true;
                }

                if (sending_rtp_via_tcp == false && ((now - connection.time_since_last_rtsp_keepalive).TotalSeconds > timeout_in_seconds))
                {
                    Console.WriteLine("Removing session " + connection.video_session_id + " due to TIMEOUT");
                    connection.play = false; // stop sending data
                    if (connection.video_udp_pair != null)
                    {
                        connection.video_udp_pair.Stop();
                        connection.video_udp_pair = null;
                    }
                    connection.listener.Dispose();

                    rtsp_list.Remove(connection);
                    continue;
                }
                else if (connection.play)
                {
                    current_rtp_play_count++;
                }
            }
        }
        UpdateClients();

        // Take the YUV image and encode it into a H264 NAL
        // This returns a NAL with no headers (no 00 00 00 01 header and no 32 bit sizes)
        Console.WriteLine(current_rtp_count + " RTSP clients connected. " + current_rtp_play_count + " RTSP clients in PLAY mode");

        if (current_rtp_play_count == 0)
        {
            return;
        }

        byte[] raw_video_nal = obj.Data;
        bool   isKeyframe    = obj.KeyFrame;

        List <byte[]> nal_array = new List <byte[]>();

        // We may want to add the SPS and PPS to the H264 stream as in-band data.
        // This may be of use if the client did not parse the SPS/PPS in the SDP
        // or if the H264 encoder changes properties (eg a new resolution or framerate which
        // gives a new SPS or PPS).
        // Also looking towards H265, the VPS/SPS/PPS do not need to be in the SDP so would be added here.

#if false
        Boolean add_sps_pps_to_keyframe = true;

        if (add_sps_pps_to_keyframe && isKeyframe)
        {
            nal_array.Add(raw_sps);
            nal_array.Add(raw_pps);
        }
#endif

        // add the rest of the NALs
        nal_array.Add(raw_video_nal);

        /*
         * uint timestamp_ms = (uint)(obj.Time / 1000); // CHECK THIS
         * Console.WriteLine("timestamp_ms: {0}", timestamp_ms);
         * UInt32 rtp_timestamp = timestamp_ms * 90; // 90kHz clock
         */
        uint rtp_timestamp = (uint)((obj.Time & 0xFFFFFFFF) * 90 / 1000);
        if (firstStamp == 0)
        {
            firstStamp = rtp_timestamp;
        }
        rtp_timestamp -= firstStamp;
        if (rtp_timestamp - lastStamp > 90000)
        {
            uint override_timestamp = lastStamp + 6000;
            firstStamp    = (uint)((obj.Time & 0xFFFFFFFF) * 90 / 1000) - override_timestamp;
            rtp_timestamp = override_timestamp;
        }
        lastStamp = rtp_timestamp;

        uint timestamp_ms = rtp_timestamp / 90;

        // Build a list of 1 or more RTP packets
        // The last packet will have the M bit set to '1'
        List <byte[]> rtp_packets = new List <byte[]>();

        for (int x = 0; x < nal_array.Count; x++)
        {
            byte[] raw_nal  = nal_array[x];
            bool   last_nal = false;
            if (x == nal_array.Count - 1)
            {
                last_nal = true; // last NAL in our nal_array
            }

            // The H264 Payload could be sent as one large RTP packet (assuming the receiver can handle it)
            // or as a Fragmented Data, split over several RTP packets with the same Timestamp.
            bool fragmenting = false;
            int  packetMTU   = 1400; //65500; //
            if (raw_nal.Length > packetMTU)
            {
                fragmenting = true;
            }


            if (fragmenting == false)
            {
                // Put the whole NAL into one RTP packet.
                // Note some receivers will have maximum buffers and be unable to handle large RTP packets.
                // Also with RTP over RTSP there is a limit of 65535 bytes for the RTP packet.

                byte[] rtp_packet = new byte[12 + raw_nal.Length]; // 12 is header size when there are no CSRCs or extensions
                // Create an single RTP fragment

                // RTP Packet Header
                // 0 - Version, P, X, CC, M, PT and Sequence Number
                //32 - Timestamp. H264 uses a 90kHz clock
                //64 - SSRC
                //96 - CSRCs (optional)
                //nn - Extension ID and Length
                //nn - Extension header

                int rtp_version      = 2;
                int rtp_padding      = 0;
                int rtp_extension    = 0;
                int rtp_csrc_count   = 0;
                int rtp_marker       = (last_nal == true ? 1 : 0); // set to 1 if the last NAL in the array
                int rtp_payload_type = 96;

                RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);

                uint empty_sequence_id = 0;
                RTPPacketUtil.WriteSequenceNumber(rtp_packet, empty_sequence_id);

                RTPPacketUtil.WriteTS(rtp_packet, rtp_timestamp);

                uint empty_ssrc = 0;
                RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);

                // Now append the raw NAL
                System.Array.Copy(raw_nal, 0, rtp_packet, 12, raw_nal.Length);

                rtp_packets.Add(rtp_packet);
            }
            else
            {
                int data_remaining = raw_nal.Length;
                int nal_pointer    = 0;
                int start_bit      = 1;
                int end_bit        = 0;

                // consume first byte of the raw_nal. It is used in the FU header
                byte first_byte = raw_nal[0];
                nal_pointer++;
                data_remaining--;

                while (data_remaining > 0)
                {
                    int payload_size = Math.Min(packetMTU, data_remaining);
                    if (data_remaining - payload_size == 0)
                    {
                        end_bit = 1;
                    }

                    byte[] rtp_packet = new byte[12 + 2 + payload_size]; // 12 is header size. 2 bytes for FU-A header. Then payload

                    // RTP Packet Header
                    // 0 - Version, P, X, CC, M, PT and Sequence Number
                    //32 - Timestamp. H264 uses a 90kHz clock
                    //64 - SSRC
                    //96 - CSRCs (optional)
                    //nn - Extension ID and Length
                    //nn - Extension header

                    int rtp_version      = 2;
                    int rtp_padding      = 0;
                    int rtp_extension    = 0;
                    int rtp_csrc_count   = 0;
                    int rtp_marker       = (last_nal == true ? 1 : 0); // Marker set to 1 on last packet
                    int rtp_payload_type = 96;

                    RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);

                    uint empty_sequence_id = 0;
                    RTPPacketUtil.WriteSequenceNumber(rtp_packet, empty_sequence_id);

                    RTPPacketUtil.WriteTS(rtp_packet, rtp_timestamp);

                    uint empty_ssrc = 0;
                    RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);

                    // Now append the Fragmentation Header (with Start and End marker) and part of the raw_nal
                    byte f_bit = 0;
                    byte nri   = (byte)((first_byte >> 5) & 0x03); // Part of the 1st byte of the Raw NAL (NAL Reference ID)
                    byte type  = 28;                               // FU-A Fragmentation

                    rtp_packet[12] = (byte)((f_bit << 7) + (nri << 5) + type);
                    rtp_packet[13] = (byte)((start_bit << 7) + (end_bit << 6) + (0 << 5) + (first_byte & 0x1F));

                    System.Array.Copy(raw_nal, nal_pointer, rtp_packet, 14, payload_size);
                    nal_pointer    = nal_pointer + payload_size;
                    data_remaining = data_remaining - payload_size;

                    rtp_packets.Add(rtp_packet);

                    start_bit = 0;
                }
            }
        }

        lock (rtsp_list)
        {
            // Go through each RTSP connection and output the NAL on the Video Session
            foreach (RTSPConnection connection in rtsp_list.ToArray()) // ToArray makes a temp copy of the list.
                                                                       // This lets us delete items in the foreach
                                                                       // eg when there is Write Error
            {
                // Only process Sessions in Play Mode
                if (connection.play == false)
                {
                    continue;
                }

                string connection_type = "";
                if (connection.video_client_transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.TCP)
                {
                    connection_type = "TCP";
                }
                if (connection.video_client_transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP &&
                    connection.video_client_transport.IsMulticast == false)
                {
                    connection_type = "UDP";
                }
                if (connection.video_client_transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP &&
                    connection.video_client_transport.IsMulticast == true)
                {
                    connection_type = "Multicast";
                }
                Console.WriteLine("Sending video session " + connection.video_session_id + " " + connection_type + " Timestamp(ms)=" + timestamp_ms + ". RTP timestamp=" + rtp_timestamp + ". Sequence=" + connection.video_sequence_number);

                // There could be more than 1 RTP packet (if the data is fragmented)
                bool write_error = false;
                foreach (byte[] rtp_packet in rtp_packets)
                {
                    // Add the specific data for each transmission
                    RTPPacketUtil.WriteSequenceNumber(rtp_packet, connection.video_sequence_number);
                    connection.video_sequence_number++;

                    // Add the specific SSRC for each transmission
                    RTPPacketUtil.WriteSSRC(rtp_packet, connection.ssrc);


                    // Send as RTP over RTSP (Interleaved)
                    if (connection.video_transport_reply.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.TCP)
                    {
                        int    video_channel = connection.video_transport_reply.Interleaved.First; // second is for RTCP status messages)
                        object state         = new object();
                        try
                        {
                            // send the whole NAL. With RTP over RTSP we do not need to Fragment the NAL (as we do with UDP packets or Multicast)
                            //session.listener.BeginSendData(video_channel, rtp_packet, new AsyncCallback(session.listener.EndSendData), state);
                            connection.listener.SendData(video_channel, rtp_packet);
                        }
                        catch
                        {
                            Console.WriteLine("Error writing to listener " + connection.listener.RemoteAdress);
                            write_error = true;
                            break; // exit out of foreach loop
                        }
                    }

                    // Send as RTP over UDP
                    if (connection.video_transport_reply.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && connection.video_transport_reply.IsMulticast == false)
                    {
                        try
                        {
                            // send the whole NAL. ** We could fragment the RTP packet into smaller chuncks that fit within the MTU
                            // Send to the IP address of the Client
                            // Send to the UDP Port the Client gave us in the SETUP command
                            connection.video_udp_pair.Write_To_Data_Port(rtp_packet, connection.client_hostname, connection.video_client_transport.ClientPort.First);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine("UDP Write Exception " + e.ToString());
                            Console.WriteLine("Error writing to listener " + connection.listener.RemoteAdress);
                            write_error = true;
                            break; // exit out of foreach loop
                        }
                    }

                    // TODO. Add Multicast
                }
                if (write_error)
                {
                    Console.WriteLine("Removing session " + connection.video_session_id + " due to write error");
                    connection.play = false; // stop sending data
                    if (connection.video_udp_pair != null)
                    {
                        connection.video_udp_pair.Stop();
                        connection.video_udp_pair = null;
                    }
                    connection.listener.Dispose();
                    rtsp_list.Remove(connection); // remove the session. It is dead
                }
            }
        }
        UpdateClients();
    }