C#使用MJpeg实现视频流发送与显示

1、发送视频流:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Mjpeg
{
    public class MJpegStreamingServer
    {
        private static string _contentLengthString = "__PayloadHeaderContentLength__";
        private AsyncTcpServer _server;
        private ConcurrentDictionary<string, TcpClient> _clients;

        public MJpegStreamingServer(int listenPort)
          : this(listenPort, "--dennisgao")
        {
        }

        public MJpegStreamingServer(int listenPort, string boundary)
        {
            Port = listenPort;
            Boundary = boundary;

            _server = new AsyncTcpServer(Port);
            _server.Encoding = Encoding.ASCII;
            _clients = new ConcurrentDictionary<string, TcpClient>();
        }

        /// <summary>
        /// 监听的端口
        /// </summary>
        public int Port { get; private set; }

        /// <summary>
        /// 分隔符
        /// </summary>
        public string Boundary { get; private set; }

        /// <summary>
        /// 流头部
        /// </summary>
        public string StreamHeader
        {
            get
            {
                return "HTTP/1.1 200 OK" +
                    "\r\n" +
                         "Content-Type: multipart/x-mixed-replace; boundary=" + this.Boundary +
                          "\r\n";
            }
        }

        /// <summary>
        /// 图片头部
        /// </summary>
        public string PayloadHeader
        {
            get
            {
                return "\r\n" +
                           this.Boundary +
                               "\r\n" +
                             "Content-Type: image/jpeg" +
                             "\r\n" +
                             "Content-Length: " + _contentLengthString +
                             "\r\n\r\n";
            }
        }

        public void Start()
        {
            _server.Start(10);
            _server.ClientConnected += new EventHandler<TcpClientConnectedEventArgs>(OnClientConnected);
            _server.ClientDisconnected += new EventHandler<TcpClientDisconnectedEventArgs>(OnClientDisconnected);
        }

        public void Stop()
        {
            _server.Stop();
            _server.ClientConnected -= new EventHandler<TcpClientConnectedEventArgs>(OnClientConnected);
            _server.ClientDisconnected -= new EventHandler<TcpClientDisconnectedEventArgs>(OnClientDisconnected);
        }

        private void OnClientConnected(object sender, TcpClientConnectedEventArgs e)
        {
            _clients.AddOrUpdate(e.TcpClient.Client.RemoteEndPoint.ToString(), e.TcpClient, (n, o) => { return e.TcpClient; });
        }

        private void OnClientDisconnected(object sender, TcpClientDisconnectedEventArgs e)
        {
            TcpClient clientToBeThrowAway;
            _clients.TryRemove(e.TcpClient.Client.RemoteEndPoint.ToString(), out clientToBeThrowAway);
        }

        public void Write(Bitmap image)
        {
            if (_server.IsRunning)
            {
                byte[] payload = BytesOf(image);

                WriteStreamHeader();
                WritePayload(payload);
            }
        }

        private void WriteStreamHeader()
        {
            if (_clients.Count > 0)
            {
                foreach (var item in _clients)
                {
                    //Logger.Debug(string.Format(CultureInfo.InvariantCulture,  "Writing stream header, {0}, {1}{2}", item.Key, Environment.NewLine, StreamHeader));

                    _server.Send(item.Value, StreamHeader);

                    TcpClient clientToBeThrowAway;
                    _clients.TryRemove(item.Key, out clientToBeThrowAway);
                }
            }
        }

        private void WritePayload(byte[] payload)
        {
            string payloadHeader = this.PayloadHeader.Replace(_contentLengthString, payload.Length.ToString());
            string payloadTail = "\r\n";

            //Logger.Debug(string.Format(CultureInfo.InvariantCulture, "Writing payload header, {0}{1}", Environment.NewLine, payloadHeader));

            byte[] payloadHeaderBytes = _server.Encoding.GetBytes(payloadHeader);
            byte[] payloadTailBytes = _server.Encoding.GetBytes(payloadTail);
            byte[] packet = new byte[payloadHeaderBytes.Length + payload.Length + payloadTailBytes.Length];
            Buffer.BlockCopy(payloadHeaderBytes, 0, packet, 0, payloadHeaderBytes.Length);
            Buffer.BlockCopy(payload, 0, packet, payloadHeaderBytes.Length, payload.Length);
            Buffer.BlockCopy(payloadTailBytes, 0, packet, payloadHeaderBytes.Length + payload.Length, payloadTailBytes.Length);

            _server.SendAll(packet);
        }

        private byte[] BytesOf(Bitmap image)
        {
            MemoryStream ms = new MemoryStream();
            image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);

            byte[] payload = ms.ToArray();

            return payload;
        }
    }
}

用到的AsyncTcpServer类:

using System.Collections.Generic;
using System.Net.Sockets;
using System.Net;
using System.Reflection.Emit;
using System.Text;
using System;

namespace Mjpeg
{
    /// <summary>
    /// 异步TCP服务器
    /// </summary>
    public class AsyncTcpServer : IDisposable
    {
        #region Fields

        private TcpListener listener;
        private List<TcpClientState> clients;
        private bool disposed = false;

        #endregion

        #region Ctors

        /// <summary>
        /// 异步TCP服务器
        /// </summary>
        /// <param name="listenPort">监听的端口</param>
        public AsyncTcpServer(int listenPort)
          : this(IPAddress.Any, listenPort)
        {
        }

        /// <summary>
        /// 异步TCP服务器
        /// </summary>
        /// <param name="localEP">监听的终结点</param>
        public AsyncTcpServer(IPEndPoint localEP)
          : this(localEP.Address, localEP.Port)
        {
        }

        /// <summary>
        /// 异步TCP服务器
        /// </summary>
        /// <param name="localIPAddress">监听的IP地址</param>
        /// <param name="listenPort">监听的端口</param>
        public AsyncTcpServer(IPAddress localIPAddress, int listenPort)
        {
            Address = localIPAddress;
            Port = listenPort;
            this.Encoding = Encoding.Default;

            clients = new List<TcpClientState>();

            listener = new TcpListener(Address, Port);
            listener.AllowNatTraversal(true);
        }

        #endregion

        #region Properties

        /// <summary>
        /// 服务器是否正在运行
        /// </summary>
        public bool IsRunning { get; private set; }
        /// <summary>
        /// 监听的IP地址
        /// </summary>
        public IPAddress Address { get; private set; }
        /// <summary>
        /// 监听的端口
        /// </summary>
        public int Port { get; private set; }
        /// <summary>
        /// 通信使用的编码
        /// </summary>
        public Encoding Encoding { get; set; }

        #endregion

        #region Server

        /// <summary>
        /// 启动服务器
        /// </summary>
        /// <returns>异步TCP服务器</returns>
        public AsyncTcpServer Start()
        {
            if (!IsRunning)
            {
                IsRunning = true;
                listener.Start();
                listener.BeginAcceptTcpClient(
                  new AsyncCallback(HandleTcpClientAccepted), listener);
            }
            return this;
        }

        /// <summary>
        /// 启动服务器
        /// </summary>
        /// <param name="backlog">
        /// 服务器所允许的挂起连接序列的最大长度
        /// </param>
        /// <returns>异步TCP服务器</returns>
        public AsyncTcpServer Start(int backlog)
        {
            if (!IsRunning)
            {
                IsRunning = true;
                listener.Start(backlog);
                listener.BeginAcceptTcpClient(
                  new AsyncCallback(HandleTcpClientAccepted), listener);
            }
            return this;
        }

        /// <summary>
        /// 停止服务器
        /// </summary>
        /// <returns>异步TCP服务器</returns>
        public AsyncTcpServer Stop()
        {
            if (IsRunning)
            {
                IsRunning = false;
                listener.Stop();

                lock (this.clients)
                {
                    for (int i = 0; i < this.clients.Count; i++)
                    {
                        this.clients[i].TcpClient.Client.Disconnect(false);
                    }
                    this.clients.Clear();
                }

            }
            return this;
        }

        #endregion

        #region Receive

        private void HandleTcpClientAccepted(IAsyncResult ar)
        {
            if (IsRunning)
            {
                TcpListener tcpListener = (TcpListener)ar.AsyncState;

                TcpClient tcpClient = tcpListener.EndAcceptTcpClient(ar);
                byte[] buffer = new byte[tcpClient.ReceiveBufferSize];

                TcpClientState internalClient
                  = new TcpClientState(tcpClient, buffer);
                lock (this.clients)
                {
                    this.clients.Add(internalClient);
                    RaiseClientConnected(tcpClient);
                }

                NetworkStream networkStream = internalClient.NetworkStream;
                networkStream.BeginRead(
                  internalClient.Buffer,
                  0,
                  internalClient.Buffer.Length,
                  HandleDatagramReceived,
                  internalClient);

                tcpListener.BeginAcceptTcpClient(
                  new AsyncCallback(HandleTcpClientAccepted), ar.AsyncState);
            }
        }

        private void HandleDatagramReceived(IAsyncResult ar)
        {
            if (IsRunning)
            {
                TcpClientState internalClient = (TcpClientState)ar.AsyncState;
                NetworkStream networkStream = internalClient.NetworkStream;

                int numberOfReadBytes = 0;
                try
                {
                    numberOfReadBytes = networkStream.EndRead(ar);
                }
                catch
                {
                    numberOfReadBytes = 0;
                }

                if (numberOfReadBytes == 0)
                {
                    // connection has been closed
                    lock (this.clients)
                    {
                        this.clients.Remove(internalClient);
                        RaiseClientDisconnected(internalClient.TcpClient);
                        return;
                    }
                }

                // received byte and trigger event notification
                byte[] receivedBytes = new byte[numberOfReadBytes];
                Buffer.BlockCopy(
                  internalClient.Buffer, 0,
                  receivedBytes, 0, numberOfReadBytes);
                RaiseDatagramReceived(internalClient.TcpClient, receivedBytes);
                RaisePlaintextReceived(internalClient.TcpClient, receivedBytes);

                // continue listening for tcp datagram packets
                networkStream.BeginRead(
                  internalClient.Buffer,
                  0,
                  internalClient.Buffer.Length,
                  HandleDatagramReceived,
                  internalClient);
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// 接收到数据报文事件
        /// </summary>
        public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> DatagramReceived;
        /// <summary>
        /// 接收到数据报文明文事件
        /// </summary>
        public event EventHandler<TcpDatagramReceivedEventArgs<string>> PlaintextReceived;

        private void RaiseDatagramReceived(TcpClient sender, byte[] datagram)
        {
            if (DatagramReceived != null)
            {
                DatagramReceived(this, new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram));
            }
        }

        private void RaisePlaintextReceived(TcpClient sender, byte[] datagram)
        {
            if (PlaintextReceived != null)
            {
                PlaintextReceived(this, new TcpDatagramReceivedEventArgs<string>(
                  sender, this.Encoding.GetString(datagram, 0, datagram.Length)));
            }
        }

        /// <summary>
        /// 与客户端的连接已建立事件
        /// </summary>
        public event EventHandler<TcpClientConnectedEventArgs> ClientConnected;
        /// <summary>
        /// 与客户端的连接已断开事件
        /// </summary>
        public event EventHandler<TcpClientDisconnectedEventArgs> ClientDisconnected;

        private void RaiseClientConnected(TcpClient tcpClient)
        {
            if (ClientConnected != null)
            {
                ClientConnected(this, new TcpClientConnectedEventArgs(tcpClient));
            }
        }

        private void RaiseClientDisconnected(TcpClient tcpClient)
        {
            if (ClientDisconnected != null)
            {
                ClientDisconnected(this, new TcpClientDisconnectedEventArgs(tcpClient));
            }
        }

        #endregion

        #region Send

        /// <summary>
        /// 发送报文至指定的客户端
        /// </summary>
        /// <param name="tcpClient">客户端</param>
        /// <param name="datagram">报文</param>
        public void Send(TcpClient tcpClient, byte[] datagram)
        {
            if (!IsRunning)
                throw new InvalidProgramException("This TCP server has not been started.");

            if (tcpClient == null)
                throw new ArgumentNullException("tcpClient");

            if (datagram == null)
                throw new ArgumentNullException("datagram");

            tcpClient.GetStream().BeginWrite(
              datagram, 0, datagram.Length, HandleDatagramWritten, tcpClient);
        }

        private void HandleDatagramWritten(IAsyncResult ar)
        {
            ((TcpClient)ar.AsyncState).GetStream().EndWrite(ar);
        }

        /// <summary>
        /// 发送报文至指定的客户端
        /// </summary>
        /// <param name="tcpClient">客户端</param>
        /// <param name="datagram">报文</param>
        public void Send(TcpClient tcpClient, string datagram)
        {
            Send(tcpClient, this.Encoding.GetBytes(datagram));
        }

        /// <summary>
        /// 发送报文至所有客户端
        /// </summary>
        /// <param name="datagram">报文</param>
        public void SendAll(byte[] datagram)
        {
            if (!IsRunning)
                throw new InvalidProgramException("This TCP server has not been started.");

            for (int i = 0; i < this.clients.Count; i++)
            {
                Send(this.clients[i].TcpClient, datagram);
            }
        }

        /// <summary>
        /// 发送报文至所有客户端
        /// </summary>
        /// <param name="datagram">报文</param>
        public void SendAll(string datagram)
        {
            if (!IsRunning)
                throw new InvalidProgramException("This TCP server has not been started.");

            SendAll(this.Encoding.GetBytes(datagram));
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing,
        /// releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release
        /// both managed and unmanaged resources; <c>false</c>
        /// to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    try
                    {
                        Stop();

                        if (listener != null)
                        {
                            listener = null;
                        }
                    }
                    catch (SocketException ex)
                    {
                        //ExceptionHandler.Handle(ex);
                    }
                }

                disposed = true;
            }
        }

        #endregion
    }
}

调用:实现截屏发送

 MJpegStreamingServer server = new MJpegStreamingServer(1022);
            server.Start();
            Task.Run(() =>
            {
                while (true)
                {
                    try
                    {
                        var bit = CaptureActiveScreen();
                        server.Write(bit);
                        Thread.Sleep(50);
                    }
                    catch { }
                };

            });

截屏代码:

/// <summary>
        /// 截屏
        /// </summary>
        /// <returns></returns>
        public Bitmap CaptureActiveScreen()
        {
            // 创建一个和当前屏幕一样大小的Bitmap
            Rectangle bounds = Screen.GetBounds(Point.Empty);
            Bitmap bmp = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);

            // 创建一个画布
            Graphics g = Graphics.FromImage(bmp);

            // 截取整个屏幕
            g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);

            // 释放画布资源
            g.Dispose();

            return bmp;
        }

2、显示Mjpeg流:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Mjpeg
{
    public class MjpegUtils : IDisposable
    {
        public bool IsExit = false;
        /// <summary>
        /// 播放MJPEG视频流
        /// </summary>
        /// <param name="mjpegStreamUrl">MJPEG视频流地址</param>
        /// <param name="accessImageHandler">读到图片后处理</param>
        /// <param name="sleep">每次读取文件睡眠时间,太长会导致卡顿,太短可能频繁矫正流</param>
        public MjpegUtils(string mjpegStreamUrl, Action<byte[]> accessImageHandler, int sleep = 50)
        {
            HttpWebRequest hwRequest = (System.Net.HttpWebRequest)WebRequest.Create(mjpegStreamUrl);
            hwRequest.Method = "GET";
            HttpWebResponse hwResponse = (HttpWebResponse)hwRequest.GetResponse();
            string boundary = hwRequest.Headers["Content-Type:"];
            //响应流中没有响应头信息,全是响应体内容
            Stream stream = hwResponse.GetResponseStream();
            string headerName = "Content-Length:";
            StringBuilder sb = new StringBuilder();
            int len = 1024;
            while (true)
            {
                while (true)
                {
                    char c = (char)stream.ReadByte();
                    //Console.Write(c);
                    if (c == '\n')
                    {
                        break;
                    }
                    sb.Append(c);
                }
                string line = sb.ToString();
                sb.Remove(0, sb.Length);
                int i = line.IndexOf(headerName);
                if (i != -1)
                {

                    int imageFileLength = Convert.ToInt32(line.Substring(i + headerName.Length).Trim());
                    stream.Read(new byte[2], 0, 2);
                    byte[] imageFileBytes = new byte[imageFileLength];
                    stream.Read(imageFileBytes, 0, imageFileBytes.Length);
                    //Console.WriteLine("文件头:" + imageFileBytes[0].ToString("X") + " " + imageFileBytes[1].ToString("X") + " " + imageFileBytes[2].ToString("X") + " " + imageFileBytes[3].ToString("X") + " " + imageFileBytes[4].ToString("X"));
                    //Console.WriteLine("文件尾:" + imageFileBytes[imageFileLength - 2].ToString("X") + " " + imageFileBytes[imageFileLength - 1].ToString("X"));
                    if (imageFileBytes[imageFileLength - 2].ToString("X") != "FF" && imageFileBytes[imageFileLength - 1].ToString("X") != "D9")
                    {
                        //Console.WriteLine("开始矫正...");
                        //修正
                        char l = '0';
                        while (true)
                        {
                            char c = (char)stream.ReadByte();
                            if (l == '-' && c == '-')
                            {
                                break;
                            }
                            l = c;
                        }
                    }
                    else
                    {
                        //读取图片成功!
                        accessImageHandler(imageFileBytes);
                    }
                    Thread.Sleep(sleep);
                }

            }
            stream.Close();
            hwResponse.Close();
            Console.Read();
        }

        public int StreamFindContentLength(Stream stream)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append((char)stream.ReadByte());
            sb.Append((char)stream.ReadByte());
            sb.Append((char)stream.ReadByte());
            sb.Append((char)stream.ReadByte());
            Console.WriteLine("num:" + sb);
            int num = Convert.ToInt32(sb.ToString().Trim());
            //跳过\r\n
            char c = '0';
            do
            {
                c = (char)stream.ReadByte();
            } while (c == '\r' || c == '\n');
            return num;
        }

        public void Dispose()
        {
            IsExit = true;
        }



    }
}

调用:

    var thread = new Thread(new ThreadStart(delegate ()
             {
                 var mjpegUtils = new MjpegUtils("http://127.0.0.1:1022", delegate (byte[] bytes)
                   {
                       //Console.WriteLine(bytes.Length);
                       MemoryStream ms = new MemoryStream(bytes);
                       pictureBox1.Image = Image.FromStream(ms);
                       ms.Close();
                   }, 50);
             }));
            thread.IsBackground = true;
            thread.Start();

最终效果:

源码下载

 参考文章:

https://blog.csdn.net/weixin_33709364/article/details/86419401

https://www.jb51.net/article/206675.htm

https://www.cnblogs.com/gaochundong/archive/2013/04/14/csharp_async_tcp_server.html

https://www.cnblogs.com/gaochundong/archive/2013/04/14/csharp_async_tcp_client.html

 

posted on 2024-06-22 17:18  梦琪小生  阅读(7)  评论(0编辑  收藏  举报

导航