hawkeye camera support(not tested)
This commit is contained in:
800
framework/Inspectron.HawkEye/RTSP/Server/RtspServer.cs
Normal file
800
framework/Inspectron.HawkEye/RTSP/Server/RtspServer.cs
Normal file
@@ -0,0 +1,800 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Inspectron.HawkEye.RTSP.Messages;
|
||||
|
||||
// RTSP Server Example (c) Roger Hardiman, 2016, 2018
|
||||
// Released uder the MIT Open Source Licence
|
||||
//
|
||||
// Re-uses some code from the Multiplexer example of SharpRTSP
|
||||
//
|
||||
// This example simulates a live RTSP video stream, for example a CCTV Camera
|
||||
// It creates a Video Source (a test card) that creates a YUV Image
|
||||
// The image is then encoded as H264 data using a very basic H264 Encoder
|
||||
// The H264 data (the NALs) are sent to the RTSP clients
|
||||
// Video is sent in UDP Mode or TCP Mode (ie RTP over RTSP mode)
|
||||
|
||||
// The Tiny H264 Encoder is a 100% .NET encoder which is lossless and creates large bitstreams as
|
||||
// there is no compression. It is limited to 128x96 resolution. However it makes it easy to write a quick
|
||||
// demo without needing native APIs or cross compiled C libraries for H264
|
||||
|
||||
namespace Inspectron.HawkEye.RTSP.Server
|
||||
{
|
||||
public class RtspServer : IDisposable
|
||||
{
|
||||
const int h264_width = 192; // Tiny needs 128x96
|
||||
const int h264_height = 128;
|
||||
const int h264_fps = 25;
|
||||
|
||||
const uint global_ssrc = 0x4321FADE; // 8 hex digits
|
||||
|
||||
private TcpListener _RTSPServerListener;
|
||||
private ManualResetEvent _Stopping;
|
||||
private Thread _ListenTread;
|
||||
|
||||
private TestCard video_source = null;
|
||||
private SimpleH264Encoder h264_encoder = null;
|
||||
//private TinyH264Encoder h264_encoder = null;
|
||||
|
||||
byte[] raw_sps = null;
|
||||
byte[] raw_pps = null;
|
||||
|
||||
List<RTSPConnection> rtsp_list = new List<RTSPConnection>(); // list of RTSP Listeners
|
||||
|
||||
Random rnd = new Random();
|
||||
int session_handle = 1;
|
||||
|
||||
Authentication auth = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RTSPServer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="aPortNumber">A numero port.</param>
|
||||
/// <param name="username">username.</param>
|
||||
/// <param name="password">password.</param>
|
||||
public RtspServer(int portNumber, String username, String password)
|
||||
{
|
||||
if (portNumber < System.Net.IPEndPoint.MinPort || portNumber > System.Net.IPEndPoint.MaxPort)
|
||||
throw new ArgumentOutOfRangeException("aPortNumber", portNumber, "Port number must be between System.Net.IPEndPoint.MinPort and System.Net.IPEndPoint.MaxPort");
|
||||
Contract.EndContractBlock();
|
||||
|
||||
if (String.IsNullOrEmpty(username) == false
|
||||
&& String.IsNullOrEmpty(password) == false) {
|
||||
String realm = "SharpRTSPServer";
|
||||
auth = new Authentication(username,password,realm,Authentication.Type.Digest);
|
||||
} else {
|
||||
auth = null;
|
||||
}
|
||||
|
||||
RtspUtils.RegisterUri();
|
||||
_RTSPServerListener = new TcpListener(IPAddress.Any, portNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the listen.
|
||||
/// </summary>
|
||||
public void StartListen()
|
||||
{
|
||||
_RTSPServerListener.Start();
|
||||
|
||||
_Stopping = new ManualResetEvent(false);
|
||||
_ListenTread = new Thread(new ThreadStart(AcceptConnection));
|
||||
_ListenTread.Start();
|
||||
|
||||
// Initialise the H264 encoder
|
||||
h264_encoder = new SimpleH264Encoder(h264_width, h264_height, h264_fps);
|
||||
//h264_encoder = new TinyH264Encoder(); // hard coded to 192x128
|
||||
|
||||
// Start the VideoSource
|
||||
video_source = new TestCard(h264_width, h264_height, h264_fps);
|
||||
video_source.ReceivedYUVFrame += video_source_ReceivedYUVFrame;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Accepts the connection.
|
||||
/// </summary>
|
||||
private void AcceptConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!_Stopping.WaitOne(0))
|
||||
{
|
||||
// Wait for an incoming TCP Connection
|
||||
TcpClient oneClient = _RTSPServerListener.AcceptTcpClient();
|
||||
Console.WriteLine("Connection from " + oneClient.Client.RemoteEndPoint.ToString());
|
||||
|
||||
// Hand the incoming TCP connection over to the RTSP classes
|
||||
var rtsp_socket = new RtspTcpTransport(oneClient);
|
||||
RtspListener newListener = new RtspListener(rtsp_socket);
|
||||
newListener.MessageReceived += RTSP_Message_Received;
|
||||
//RTSPDispatcher.Instance.AddListener(newListener);
|
||||
|
||||
// Add the RtspListener to the RTSPConnections List
|
||||
lock (rtsp_list) {
|
||||
RTSPConnection new_connection = new RTSPConnection();
|
||||
new_connection.listener = newListener;
|
||||
new_connection.client_hostname = newListener.RemoteAdress.Split(':')[0];
|
||||
new_connection.ssrc = global_ssrc;
|
||||
|
||||
new_connection.time_since_last_rtsp_keepalive = DateTime.UtcNow;
|
||||
new_connection.video_time_since_last_rtcp_keepalive = DateTime.UtcNow;
|
||||
|
||||
rtsp_list.Add(new_connection);
|
||||
}
|
||||
|
||||
newListener.Start();
|
||||
}
|
||||
}
|
||||
catch (SocketException error)
|
||||
{
|
||||
// _logger.Warn("Got an error listening, I have to handle the stopping which also throw an error", error);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
// _logger.Error("Got an error listening...", error);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void StopListen()
|
||||
{
|
||||
_RTSPServerListener.Stop();
|
||||
_Stopping.Set();
|
||||
_ListenTread.Join();
|
||||
}
|
||||
|
||||
#region IDisposable Membres
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
StopListen();
|
||||
_Stopping.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Process each RTSP message that is received
|
||||
private void RTSP_Message_Received(object sender, RtspChunkEventArgs e)
|
||||
{
|
||||
// Cast the 'sender' and 'e' into the RTSP Listener (the Socket) and the RTSP Message
|
||||
RtspListener listener = sender as RtspListener;
|
||||
RtspMessage message = e.Message as RtspMessage;
|
||||
|
||||
Console.WriteLine("RTSP message received " + message);
|
||||
|
||||
|
||||
// Check if the RTSP Message has valid authentication (validating against username,password,realm and nonce)
|
||||
if (auth != null) {
|
||||
bool authorized = false;
|
||||
if (message.Headers.ContainsKey("Authorization") == true ) {
|
||||
// The Header contained Authorization
|
||||
// Check the message has the correct Authorization
|
||||
// If it does not have the correct Authorization then close the RTSP connection
|
||||
authorized = auth.IsValid(message);
|
||||
|
||||
if (authorized == false) {
|
||||
// Send a 401 Authentication Failed reply, then close the RTSP Socket
|
||||
RtspResponse authorization_response = (e.Message as RtspRequest).CreateResponse();
|
||||
authorization_response.AddHeader("WWW-Authenticate: " + auth.GetHeader());
|
||||
authorization_response.ReturnCode = 401;
|
||||
listener.SendMessage(authorization_response);
|
||||
|
||||
lock (rtsp_list) {
|
||||
foreach (RTSPConnection connection in rtsp_list.ToArray()){
|
||||
if (connection.listener == listener) {
|
||||
rtsp_list.Remove(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
listener.Dispose();
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
if ((message.Headers.ContainsKey("Authorization") == false)){
|
||||
// Send a 401 Authentication Failed with extra info in WWW-Authenticate
|
||||
// to tell the Client if we are using Basic or Digest Authentication
|
||||
RtspResponse authorization_response = (e.Message as RtspRequest).CreateResponse();
|
||||
authorization_response.AddHeader("WWW-Authenticate: " + auth.GetHeader()); // 'Basic' or 'Digest'
|
||||
authorization_response.ReturnCode = 401;
|
||||
listener.SendMessage(authorization_response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the RTSP Keepalive Timeout
|
||||
// We could check that the message is GET_PARAMETER or OPTIONS for a keepalive but instead we will update the timer on any message
|
||||
lock (rtsp_list)
|
||||
{
|
||||
foreach (RTSPConnection connection in rtsp_list)
|
||||
{
|
||||
if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress))
|
||||
{
|
||||
// found the connection
|
||||
connection.time_since_last_rtsp_keepalive = DateTime.UtcNow;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle OPTIONS message
|
||||
if (message is RtspRequestOptions)
|
||||
{
|
||||
// Create the reponse to OPTIONS
|
||||
RtspResponse options_response = (e.Message as RtspRequestOptions).CreateResponse();
|
||||
listener.SendMessage(options_response);
|
||||
}
|
||||
|
||||
// Handle DESCRIBE message
|
||||
if (message is RtspRequestDescribe)
|
||||
{
|
||||
String requested_url = (message as RtspRequestDescribe).RtspUri.ToString();
|
||||
Console.WriteLine("Request for " + requested_url);
|
||||
|
||||
// TODO. Check the requsted_url is valid. In this example we accept any RTSP URL
|
||||
|
||||
// Make the Base64 SPS and PPS
|
||||
raw_sps = h264_encoder.GetRawSPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header
|
||||
raw_pps = h264_encoder.GetRawPPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header
|
||||
String sps_str = Convert.ToBase64String(raw_sps);
|
||||
String pps_str = Convert.ToBase64String(raw_pps);
|
||||
|
||||
StringBuilder sdp = new StringBuilder();
|
||||
|
||||
// Generate the SDP
|
||||
// The sprop-parameter-sets provide the SPS and PPS for H264 video
|
||||
// The packetization-mode defines the H264 over RTP payloads used but is Optional
|
||||
sdp.Append("v=0\n");
|
||||
sdp.Append("o=user 123 0 IN IP4 0.0.0.0\n");
|
||||
sdp.Append("s=SharpRTSP Test Camera\n");
|
||||
sdp.Append("m=video 0 RTP/AVP 96\n");
|
||||
sdp.Append("c=IN IP4 0.0.0.0\n");
|
||||
sdp.Append("a=control:trackID=0\n");
|
||||
sdp.Append("a=rtpmap:96 H264/90000\n");
|
||||
sdp.Append("a=fmtp:96 profile-level-id=42A01E; sprop-parameter-sets=" + sps_str + "," + pps_str + ";\n");
|
||||
|
||||
byte[] sdp_bytes = Encoding.ASCII.GetBytes(sdp.ToString());
|
||||
|
||||
// Create the reponse to DESCRIBE
|
||||
// This must include the Session Description Protocol (SDP)
|
||||
RtspResponse describe_response = (e.Message as RtspRequestDescribe).CreateResponse();
|
||||
|
||||
describe_response.AddHeader("Content-Base: " + requested_url);
|
||||
describe_response.AddHeader("Content-Type: application/sdp");
|
||||
describe_response.Data = sdp_bytes;
|
||||
describe_response.AdjustContentLength();
|
||||
listener.SendMessage(describe_response);
|
||||
}
|
||||
|
||||
// Handle SETUP message
|
||||
if (message is RtspRequestSetup)
|
||||
{
|
||||
|
||||
//
|
||||
var setupMessage = message as RtspRequestSetup;
|
||||
|
||||
// Check the RTSP transport
|
||||
// If it is UDP or Multicast, create the sockets
|
||||
// If it is RTP over RTSP we send data via the RTSP Listener
|
||||
|
||||
// FIXME client may send more than one possible transport.
|
||||
// very rare
|
||||
RtspTransport transport = setupMessage.GetTransports()[0];
|
||||
|
||||
|
||||
// Construct the Transport: reply from the Server to the client
|
||||
RtspTransport transport_reply = new RtspTransport();
|
||||
transport_reply.SSrc = global_ssrc.ToString("X8"); // Convert to Hex, padded to 8 characters
|
||||
|
||||
if (transport.LowerTransport == RtspTransport.LowerTransportType.TCP)
|
||||
{
|
||||
// RTP over RTSP mode}
|
||||
transport_reply.LowerTransport = RtspTransport.LowerTransportType.TCP;
|
||||
transport_reply.Interleaved = new PortCouple(transport.Interleaved.First, transport.Interleaved.Second);
|
||||
}
|
||||
|
||||
UDPSocket udp_pair = null;
|
||||
if (transport.LowerTransport == RtspTransport.LowerTransportType.UDP
|
||||
&& transport.IsMulticast == false)
|
||||
{
|
||||
Boolean udp_supported = true;
|
||||
if (udp_supported) {
|
||||
// RTP over UDP mode
|
||||
// Create a pair of UDP sockets - One is for the Video, one is for the RTCP
|
||||
udp_pair = new UDPSocket(50000, 51000); // give a range of 500 pairs (1000 addresses) to try incase some address are in use
|
||||
udp_pair.DataReceived += (object local_sender, RtspChunkEventArgs local_e) => {
|
||||
// RTCP data received
|
||||
Console.WriteLine("RTCP data received " + local_sender.ToString() + " " + local_e.ToString());
|
||||
};
|
||||
udp_pair.Start(); // start listening for data on the UDP ports
|
||||
|
||||
// Pass the Port of the two sockets back in the reply
|
||||
transport_reply.LowerTransport = RtspTransport.LowerTransportType.UDP;
|
||||
transport_reply.IsMulticast = false;
|
||||
transport_reply.ClientPort = new PortCouple(udp_pair.data_port,udp_pair.control_port);
|
||||
} else {
|
||||
transport_reply = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (transport.LowerTransport == RtspTransport.LowerTransportType.UDP
|
||||
&& transport.IsMulticast == true)
|
||||
{
|
||||
// RTP over Multicast UDP mode}
|
||||
// Create a pair of UDP sockets in Multicast Mode
|
||||
// Pass the Ports of the two sockets back in the reply
|
||||
transport_reply.LowerTransport = RtspTransport.LowerTransportType.UDP;
|
||||
transport_reply.IsMulticast = true;
|
||||
transport_reply.Port = new PortCouple(7000, 7001); // FIX
|
||||
|
||||
// for now until implemented
|
||||
transport_reply = null;
|
||||
}
|
||||
|
||||
|
||||
if (transport_reply != null)
|
||||
{
|
||||
|
||||
// Update the session with transport information
|
||||
String copy_of_session_id = "";
|
||||
lock (rtsp_list)
|
||||
{
|
||||
foreach (RTSPConnection connection in rtsp_list)
|
||||
{
|
||||
if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress)) {
|
||||
// ToDo - Check the Track ID to determine if this is a SETUP for the Video Stream
|
||||
// or a SETUP for an Audio Stream.
|
||||
// In the SDP the H264 video track is TrackID 0
|
||||
|
||||
|
||||
// found the connection
|
||||
// Add the transports to the connection
|
||||
connection.video_client_transport = transport;
|
||||
connection.video_transport_reply = transport_reply;
|
||||
|
||||
// If we are sending in UDP mode, add the UDP Socket pair and the Client Hostname
|
||||
connection.video_udp_pair = udp_pair;
|
||||
|
||||
|
||||
connection.video_session_id = session_handle.ToString();
|
||||
session_handle++;
|
||||
|
||||
|
||||
// Copy the Session ID
|
||||
copy_of_session_id = connection.video_session_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RtspResponse setup_response = setupMessage.CreateResponse();
|
||||
setup_response.Headers[RtspHeaderNames.Transport] = transport_reply.ToString();
|
||||
setup_response.Session = copy_of_session_id;
|
||||
listener.SendMessage(setup_response);
|
||||
}
|
||||
else
|
||||
{
|
||||
RtspResponse setup_response = setupMessage.CreateResponse();
|
||||
// unsuported transport
|
||||
setup_response.ReturnCode = 461;
|
||||
listener.SendMessage(setup_response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle PLAY message (Sent with a Session ID)
|
||||
if (message is RtspRequestPlay)
|
||||
{
|
||||
lock (rtsp_list)
|
||||
{
|
||||
// Search for the Session in the Sessions List. Change the state to "PLAY"
|
||||
bool session_found = false;
|
||||
foreach (RTSPConnection connection in rtsp_list)
|
||||
{
|
||||
if (message.Session == connection.video_session_id) /* OR AUDIO_SESSION_ID */
|
||||
{
|
||||
// found the session
|
||||
session_found = true;
|
||||
connection.play = true; // ACTUALLY YOU COULD PAUSE JUST THE VIDEO (or JUST THE AUDIO)
|
||||
|
||||
string range = "npt=0-"; // Playing the 'video' from 0 seconds until the end
|
||||
string rtp_info = "url="+((RtspRequestPlay)message).RtspUri+";seq=" + connection.video_sequence_number; // TODO Add rtptime +";rtptime="+session.rtp_initial_timestamp;
|
||||
|
||||
// Send the reply
|
||||
RtspResponse play_response = (e.Message as RtspRequestPlay).CreateResponse();
|
||||
play_response.AddHeader("Range: " + range);
|
||||
play_response.AddHeader("RTP-Info: " + rtp_info);
|
||||
listener.SendMessage(play_response);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (session_found == false) {
|
||||
// Session ID was not found in the list of Sessions. Send a 454 error
|
||||
RtspResponse play_failed_response = (e.Message as RtspRequestPlay).CreateResponse();
|
||||
play_failed_response.ReturnCode = 454; // Session Not Found
|
||||
listener.SendMessage(play_failed_response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle PAUSE message (Sent with a Session ID)
|
||||
if (message is RtspRequestPause)
|
||||
{
|
||||
lock (rtsp_list)
|
||||
{
|
||||
// Search for the Session in the Sessions List. Change the state of "PLAY"
|
||||
foreach (RTSPConnection connection in rtsp_list)
|
||||
{
|
||||
if (message.Session == connection.video_session_id /* OR AUDIO SESSION ID */)
|
||||
{
|
||||
// found the session
|
||||
connection.play = false; // COULD HAVE PLAY/PAUSE FOR VIDEO AND AUDIO
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ToDo - only send back the OK response if the Session in the RTSP message was found
|
||||
RtspResponse pause_response = (e.Message as RtspRequestPause).CreateResponse();
|
||||
listener.SendMessage(pause_response);
|
||||
}
|
||||
|
||||
|
||||
// Handle GET_PARAMETER message, often used as a Keep Alive
|
||||
if (message is RtspRequestGetParameter)
|
||||
{
|
||||
// Create the reponse to GET_PARAMETER
|
||||
RtspResponse getparameter_response = (e.Message as RtspRequestGetParameter).CreateResponse();
|
||||
listener.SendMessage(getparameter_response);
|
||||
}
|
||||
|
||||
|
||||
// Handle TEARDOWN (sent with a Session ID)
|
||||
if (message is RtspRequestTeardown)
|
||||
{
|
||||
lock (rtsp_list)
|
||||
{
|
||||
// Search for the Session in the Sessions List.
|
||||
foreach (RTSPConnection connection in rtsp_list.ToArray()) // Convert to ToArray so we can delete from the rtp_list
|
||||
{
|
||||
if (message.Session == connection.video_session_id) // SHOULD HAVE AN AUDIO TEARDOWN AS WELL
|
||||
{
|
||||
// If this is UDP, close the transport
|
||||
// For TCP there is no transport to close (as RTP packets were interleaved into the RTSP connection)
|
||||
if (connection.video_udp_pair != null) {
|
||||
connection.video_udp_pair.Stop();
|
||||
connection.video_udp_pair = null;
|
||||
}
|
||||
|
||||
rtsp_list.Remove(connection);
|
||||
|
||||
// Close the RTSP socket
|
||||
listener.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 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)
|
||||
Boolean sending_rtp_via_tcp = false;
|
||||
if ((connection.video_client_transport != null) &&
|
||||
(connection.video_client_transport.LowerTransport == 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++;
|
||||
}
|
||||
}
|
||||
|
||||
// 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_video_nal = h264_encoder.CompressFrame(yuv_data);
|
||||
Boolean isKeyframe = true; // SimpleH264encoder and TinyH24encoder only emit keyframes
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Boolean add_sps_pps_to_keyframe = true;
|
||||
|
||||
if (add_sps_pps_to_keyframe && isKeyframe) {
|
||||
nal_array.Add(raw_sps);
|
||||
nal_array.Add(raw_pps);
|
||||
}
|
||||
|
||||
// add the rest of the NALs
|
||||
nal_array.Add(raw_video_nal);
|
||||
|
||||
|
||||
|
||||
UInt32 rtp_timestamp = timestamp_ms * 90; // 90kHz clock
|
||||
|
||||
// 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];
|
||||
Boolean 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 = 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);
|
||||
|
||||
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 = (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);
|
||||
|
||||
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 == RtspTransport.LowerTransportType.TCP) connection_type = "TCP";
|
||||
if (connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.UDP
|
||||
&& connection.video_client_transport.IsMulticast == false) connection_type = "UDP";
|
||||
if (connection.video_client_transport.LowerTransport == 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 == 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 == 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RTSPConnection
|
||||
{
|
||||
public RtspListener listener = null; // The RTSP client connection
|
||||
public bool play = false; // set to true when Session is in Play mode
|
||||
public DateTime time_since_last_rtsp_keepalive = DateTime.UtcNow; // Time since last RTSP message received - used to spot dead UDP clients
|
||||
public UInt32 ssrc = 0x12345678; // SSRC value used with this client connection
|
||||
public String client_hostname = ""; // Client Hostname/IP Address
|
||||
|
||||
public String video_session_id = ""; // RTSP Session ID used with this client connection
|
||||
public UInt16 video_sequence_number = 1; // 16 bit RTP packet sequence number used with this client connection
|
||||
public RtspTransport video_client_transport; // Transport: string from the client to the server
|
||||
public RtspTransport video_transport_reply; // Transport: reply from the server to the client
|
||||
public UDPSocket video_udp_pair = null; // Pair of UDP sockets (data and control) used when sending via UDP
|
||||
public DateTime video_time_since_last_rtcp_keepalive = DateTime.UtcNow; // Time since last RTCP message received - used to spot dead UDP clients
|
||||
|
||||
// TODO - Add Audio
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user