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
RTPClient
此时已经可以在RTPServer读取RTSP流,本地摄像机,以及本地视频文件
我们需要做的是将RTPServer读取到的视频信息转发到RTPClient中,所以得导入RTP.NET的dll文件。
先将RTP.NET的dll包复制到RTPServer以及RTPClient项目中的package中,再将之引用
如下图
导入好了RTP.NET之后,接下来就是使用它来达到传输视频的目的了
RTP.NET中有几个主要组件 - RTPSender:RTP发送者
- RTPReceive:RTP接收者
- RTPParticipant:RTP参与者
- RTPSession:RTP会话端
我们需要一个类RTPFactory来将这些组件组合起来以便于使用
代码如下
using StreamCoders.Network;using System;using System.Net;namespace RTPServer{/// <summary>/// RTP工厂/// </summary>class RTPFactory{/// <summary>/// 只读RTP会话端/// </summary>public readonly RTPSession Session;/// <summary>/// RTP发送者/// </summary>public RTPSender Sender;/// <summary>/// RTP接收者/// </summary>public RTPReceiver Receiver;/// <summary>/// RTP参与者/// </summary>private RTPParticipant participant;/// <summary>/// RTP发送参与者/// </summary>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端 ——主要内容
/// <summary>/// 图片解析事件/// </summary>/// <param name="sender"></param>/// <param name="e"></param>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按钮添加事件,在事件中实例化工厂,并将包解析事件绑定
在绑定的方法中写入如下代码
/// <summary>/// 收到RTP包进行处理/// </summary>/// <param name="packet"></param>/// <returns></returns>private bool NewRTPPacket(RTPPacket packet){//如果接受端第一次接受到某源的数据,则加入到if (!Clients.ContainsKey(packet.SSRC)){if (Clients.Count < 4)//如果发送端为4,则丢弃包{Clients.Add(packet.SSRC, new List<RTPPacket> { 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;}
这样一来,整个结构就做完了,下面来看看测试效果
需要注意的一点是,这么传输视频会卡,需要进一步完善