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;
}
这样一来,整个结构就做完了,下面来看看测试效果
需要注意的一点是,这么传输视频会卡,需要进一步完善