RTP协议转发视频流

RTP转发

做完上次的读取摄像头之后,项目需要将视频转发给客户端,所以研究了下RTP并且做了一个小程序测试功能,现在分享出来。
原料:VS2017,RTP.NET,摄像头
语言:C#
标签:EmguCV,C#,读取摄像头,NuGet,RTP
GitHub源码:https://github.com/SmithYan/RTPTransmit
百度网盘链接:https://pan.baidu.com/s/1gseTBW4_VnWrV7XuksdRzQ
提取码:hd75
复制这段内容后打开百度网盘手机App,操作更方便哦

  • 先做两个项目,一个为RTPServer,另一个为RTPClient,将
    界面都搭好,效果如图
    RTPServer
    RTP协议转发视频流_第1张图片
    RTPClient
    RTP协议转发视频流_第2张图片
    此时已经可以在RTPServer读取RTSP流,本地摄像机,以及本地视频文件
    我们需要做的是将RTPServer读取到的视频信息转发到RTPClient中,所以得导入RTP.NET的dll文件。
    先将RTP.NET的dll包复制到RTPServer以及RTPClient项目中的package中,再将之引用
    如下图RTP协议转发视频流_第3张图片
    导入好了RTP.NET之后,接下来就是使用它来达到传输视频的目的了
    RTP.NET中有几个主要组件
  • RTPSender:RTP发送者
  • RTPReceive:RTP接收者
  • RTPParticipant:RTP参与者
  • RTPSession:RTP会话端

我们需要一个类RTPFactory来将这些组件组合起来以便于使用
代码如下

using StreamCoders.Network;
    using System;
    using System.Net;
    
    namespace RTPServer
    {
        /// 
        /// RTP工厂
        /// 
        class RTPFactory
        {
            /// 
            /// 只读RTP会话端
            /// 
            public readonly RTPSession Session;
            /// 
            /// RTP发送者
            /// 
            public RTPSender Sender;
            /// 
            /// RTP接收者
            /// 
            public RTPReceiver Receiver;
            /// 
            /// RTP参与者
            /// 
            private RTPParticipant participant;
            /// 
            /// RTP发送参与者
            /// 
            private RTPParticipant senderParticipant;
    
            public RTPFactory(String RTPipAddress, int RTPport, String RTCPipAddress, int RTCPport, String forwardIP, int forwardPort)
            {
                //初始会话端
                Session = new RTPSession();
                //初始化发送者
                Sender = new RTPSender();
                //初始化接收者
                Receiver = new RTPReceiver();
    
                var senderEp = new IPEndPoint(IPAddress.Parse(forwardIP), forwardPort);
                //将发送参与者初始化绑定到目的端口
                senderParticipant = new RTPParticipant(senderEp);
                //将发送参与者添加到发送者中
                Sender.AddParticipant(senderParticipant);
                //将发送者添加到会话端中
                Session.AddSender(Sender);
    
                var rtpEp = new IPEndPoint(IPAddress.Parse(RTPipAddress), RTPport);
                var rtcpEp = new IPEndPoint(IPAddress.Parse(RTCPipAddress), RTCPport);
                //将RTP参与者初始化绑定到RTP网络端点以及RTCP网络端点
                participant = new RTPParticipant(rtpEp, rtcpEp);
                //将RTP参与者添加到RTP接收者中
                Receiver.AddParticipant(participant);
                //将RTP接收者添加到会话端中
                Session.AddReceiver(Receiver);
            }
        }
    }

搞定了这些之后就是开始使用它们
在RTPServer窗口界面双击Start添加事件,并且添加内容用以初始化变量以及绑定RTP所需要的网络端点。
绑定了之后需要在RTPServer的Capture一次次解析图像的时候将图像做成RTP包并且发送即可,需要在Capture_ImageGrabbed事件中编写如下内容
RTPServer端 ——主要内容

        /// 
        /// 图片解析事件
        /// 
        /// 
        /// 
        private void Capture_ImageGrabbed(object sender, EventArgs e)
        {
            Mat frame = new Mat();
            capture.Retrieve(frame, 0);
            IBShow.Image = frame;
            if (StartToSend)//如果可以开始传送
            {
                //新建流
                var ms = new MemoryStream();
                //将图片以Jpeg格式保存到流中
                frame.Bitmap.Save(ms, ImageFormat.Jpeg);
                //将流转化为byte数据
                var data = ms.ToArray(); //图片数据
                ms.Close();
                //Rtp 协议发送 构建rtp包
                var timeStamp = DateTime.Now.ToUniversalTime().Ticks;
                var packetSize = 1000 - 12;//一个rtp包如果是经过UDP传输的原则上不要超过1460
                //如果有数据持续发送
                while (data.Length > 0)
                {
                    //初始化RTP包开始构建
                    var rtpPacket = new RTPPacket
                    {
                        //SSRC = ,//同步源
                        Timestamp = (int)timeStamp,//时间戳
                        DataPointer = data.Take(packetSize).ToArray(),//帧数据
                        Marker = data.Length <= packetSize
                    };
                    //在RTP工厂中发送此RTP包
                    rTPFactory.Sender.Send(rtpPacket);
                    //返回剩余数据
                    data = data.Skip(packetSize).ToArray();
                }
            }
        }

发送端做完之后就得做接收端,也就是接收包以及拆包并且显示
Client:双击Connect按钮添加事件,在事件中实例化工厂,并将包解析事件绑定
在绑定的方法中写入如下代码

        /// 
        /// 收到RTP包进行处理
        /// 
        /// 
        /// 
        private bool NewRTPPacket(RTPPacket packet)
        {
            //如果接受端第一次接受到某源的数据,则加入到
            if (!Clients.ContainsKey(packet.SSRC))
            {
                if (Clients.Count < 4)//如果发送端为4,则丢弃包
                {
                    Clients.Add(packet.SSRC, new List { packet });
                }
            }
            else
            {
                Clients[packet.SSRC].Add(packet);
            }
            if (packet.Marker)//如果已经发送完毕
            {
                //丢包检测
                var orderPackets = Clients[packet.SSRC].OrderBy(rtpPacket => rtpPacket.SequenceNumber);
                if (Clients[packet.SSRC].Count != (orderPackets.Last().SequenceNumber - orderPackets.First().SequenceNumber + 1))
                {
                    //清空缓存区
                    Clients[packet.SSRC].Clear();
                    return true;
                }
                //包重组
                var count = Clients[packet.SSRC].Sum(rtpPacket => rtpPacket.DataSize);
                var newData = new byte[count];
                long offSet = 0;
                foreach (var rtpPacket in Clients[packet.SSRC])
                {
                    Array.Copy(rtpPacket.DataPointer, 0, newData, offSet, rtpPacket.DataSize);
                    offSet += rtpPacket.DataSize;
                }
                Clients[packet.SSRC].Clear();//清空缓存区

                var ms = new MemoryStream(newData);
                try
                {
                    var bmp = new Bitmap(Image.FromStream(ms));
                    PBShow.Image = bmp;
                }
                catch (Exception)
                {

                }
                finally
                {
                    ms.Close();
                }
            }
            return true;
        }

这样一来,整个结构就做完了,下面来看看测试效果

需要注意的一点是,这么传输视频会卡,需要进一步完善

你可能感兴趣的:(C#之Winfrom)