【RTP.NET入门系列 二】接收第一个RTP帧。
在RTP.NET中帧的接收,没有像接收包一样简单。
首先我们必须理解一样东西RTP的帧跟RTP的包的区别,如果了解过网络中数据包理解这两者会容易些简单说来就是RTP的帧是程序发送和接收的有用的单位比如视频传输里面里面一帧代表了一副画面,RTP包是针对RTP协议传输的最小的单位,正常情况下所有的RTP包都是一样大的(大概1K)。如果一帧的数据一个包放不下它就会被拆开,放到不同的包里面。而帧比较小的话则几个帧可能被打包到同一个包里。
所以接收帧就是将帧从包里面给弄出来。在视频传输里,大部分情况帧都是比包大,所以我只讨论这一种情况。
RTP协议为了方便我们提取帧,在每个包上弄了一个时间戳,时间戳一样的就是同一个帧的。这样就方便了。
此示例程序我参考了mediasuit.NET中 示例 RTP Reception, H.263 decoding and Display
程序流程如下:
1.将每次收到包以时间戳为关键字组成帧放到SortedDictionary<时间戳,RTP帧>集合中
2.程序另运行一个线程负责每次从集合里顺序取出已经完全接受的一帧。
这里介绍一下RTPFrame类,此类其实就是RTPPacket的集合。先看看它几个属性:
//{StreamCoders.Network.RTPFrame}
// Age: 108406
// FrameComplete: true
// HasSequenceGaps: false (是否有关连得项,这里不详细介绍)
// IsExpired: false (此帧寿命是否已到,这里不详细介绍)
// LowestSequenceNumber: 36328 (最低关联数字,关联用的,这里不详细介绍)
// PacketCount: 15
// Timestamp: 61787250
// TotalPayloadSize: 13700(总负载大小)
// UpdateAge: 108500
每个RTPPacket都有一个时间戳Timestamp,我们将时间戳相同的放到同一帧里。这样RTPFrame也有个时间戳,跟RTPPacket的一样。RTPFrame有个是否完成的标志位FrameComplete,每次获取包时,我们把它放入与他一样时间戳的里(如果集合里没有这个时间戳的帧则创建)。当所有的包都放入帧里了,则帧会自动将FrameComplete标识为true完成了。另外帧有个Age属性,这个属性代表了此帧已存在的寿命。这个实例里面将帧的寿命>500才取出,作为一个缓存用。
具体代码如下:
public partial class Form1 : Form { RTPSession session; RTPReceiver receiver; RTPParticipant participant; //缓?冲?的?帧?集?合?<时?间?戳?,?帧?> System.Collections.Generic.SortedDictionary<UInt32, RTPFrame> videoJitter; //获?取?帧?线?程? System.Threading.Thread videoThread; //线?程?是?否?允?许?允?许? bool isRunning; //标?志?获?取?集?合?中?帧?时?必?须?大?于?的?时?间? int jitterAgeThreshold = 500; //关?联?性?(?这?里?不?详?细?讲?解?)? int sequenceErrors; public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { videoJitter=new System.Collections.Generic.SortedDictionary<UInt32, RTPFrame>(); session = new RTPSession(); receiver = new RTPReceiver(); IPEndPoint rtpEp= new IPEndPoint(IPAddress.Parse("192.168.0.203"), 10000); IPEndPoint rtcpEp = new IPEndPoint(IPAddress.Parse("192.168.0.203"), 10001); participant = new RTPParticipant(rtpEp,null,null,null); session.NewRTPPacket = NewRTPPacket; session.NewRTCPPacket = NewRTCPPacket; receiver.AddParticipant(participant); session.AddReceiver(receiver); isRunning = true; videoThread = new System.Threading.Thread(new System.Threading.ThreadStart(VideoDecoderThread)); videoThread.Start(); } bool NewRTPPacket(RTPPacket packet, byte[] rawBuffer) { //Console.WriteLine(packet.ToString()); AddPacketToJitter(packet); return true; } void NewRTCPPacket(RTCPCompoundPacket packet) { Console.WriteLine(packet.ToString()); } /// <summary> /// 将?包?转?换?为?帧?放?入?缓?冲?集?合?中? /// </summary> /// <param name="packet"></param> private void AddPacketToJitter(RTPPacket packet) { if (packet == null) return; lock (videoJitter) { if (videoJitter.ContainsKey(packet.Timestamp)) { RTPFrame f = videoJitter[packet.Timestamp]; f.AddPacket(packet); return; } RTPFrame newFrame = null; newFrame = new RTPFrame(); newFrame.AddPacket(packet); videoJitter[packet.Timestamp] = newFrame; } } /// <summary> /// 获?取?集?合?里?已?经?完?成?,?保?存?时?间?最?长?的?一?帧?,?并?从?集?合?里?移?除? /// 但?是?此?帧?保?留?时?间?必?须?大?于?jitterAgeThreshold /// </summary> /// <returns>获?取?的?帧?</returns> private RTPFrame CheckJitterForCompleteFrame() { lock (videoJitter) { foreach (RTPFrame f in videoJitter.Values) { //Age属?性?标?示?获?得?帧?的?时?间?,?这?里?判?断?>某?个?值?是?进?行?缓?冲? if ((f.Age > jitterAgeThreshold && f.FrameComplete == true)) { if (f.HasSequenceGaps) sequenceErrors++; videoJitter.Remove(f.Timestamp); return f; } if (f.FrameComplete == true) break; } } return null; } /// <summary> /// 获?取?帧?线?程?,?又?叫?做?视?频?解?码?线?程? /// 一?般?情?况?下?在?这?里?获?取?帧?并?将?帧?解?码?并?显?示? /// </summary> private void VideoDecoderThread() { int iterations = 1; while (isRunning) { iterations++; RTPFrame frame = CheckJitterForCompleteFrame(); if (frame == null) { System.Threading.Thread.Sleep(0); continue; } //这?里?获?取?frame.age时?发?现?所?有?帧?好?像?都?一?样?,?原?因?是?因?为?这?个?帧?是?刚?刚?在?这?里?通?过?函?数?新?生?成?的?所?以?一?样?。?但?是?它?们?的?时?间?戳?是?不?一?样?的?。? Console.WriteLine(frame.ToString() + "\nPacketCount:" + frame.PacketCount.ToString() + "\nFrameComplete:" + frame.FrameComplete.ToString() + "\nTimestamp:" + frame.Timestamp.ToString() + "\n"); } } private void ShutdownThread() { session.Dispose(); participant.Dispose(); receiver.Dispose(); isRunning = false; videoThread.Join(); Application.Exit(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { if (isRunning) { System.Threading.Thread st = new System.Threading.Thread(new System.Threading.ThreadStart(ShutdownThread)); st.Start(); e.Cancel = true; } } }
如果有人对线程不是很熟悉也可以参照这个示例程序线程代码,我个人比较推荐。
=========================================
目录页连接:【RTP.NET入门系列 前言】微软推荐的最好用的RTP组件,免费哦。