使用C#实现RTP数据包传输参照RFC3550
c# 实现 使用 传输 数据包 RTP 参照
2023-06-13 09:14:50 时间
闲暇时折腾IP网络视频监控系统,需要支持视频帧数据包在网络内的传输。
未采用H.264或MPEG4等编码压缩方式,直接使用Bitmap图片。
由于对帧的准确到达要求不好,所以采用UDP传输。如果发生网络丢包现象则直接将帧丢弃。
为了记录数据包的传输顺序和帧的时间戳,所以研究了下RFC3550协议,采用RTP包封装视频帧。
并未全面深究,所以未使用SSRC和CSRC,因为不确切了解其用意。不过目前的实现情况已经足够了。
///<summary>
///RTP(RFC3550)协议数据包
///</summary>
///<remarks>
///TheRTPheaderhasthefollowingformat:
/// 0 1 2 3
/// 01234567890123456789012345678901
///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
///|V=2|P|X|CC |M|PT |sequencenumber |
///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
///|timestamp |
///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
///|synchronizationsource(SSRC)identifier |
///+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
///|contributingsource(CSRC)identifiers |
///|.... |
///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
///</remarks>
publicclassRtpPacket
{
///<summary>
///version(V):2bits
///RTP版本标识,当前规范定义值为2.
///ThisfieldidentifiestheversionofRTP.Theversiondefinedbythisspecificationistwo(2).
///(Thevalue1isusedbythefirstdraftversionofRTPandthevalue0isusedbytheprotocol
///initiallyimplementedinthe\vat"audiotool.)
///</summary>
publicintVersion{get{return2;}}
///<summary>
///padding(P):1bit
///如果设定padding,在报文的末端就会包含一个或者多个padding字节,这不属于payload。
///最后一个字节的padding有一个计数器,标识需要忽略多少个padding字节(包括自己)。
///一些加密算法可能需要固定块长度的padding,或者是为了在更低层数据单元中携带一些RTP报文。
///Ifthepaddingbitisset,thepacketcontainsoneormoreadditionalpaddingoctetsatthe
///endwhicharenotpartofthepayload.Thelastoctetofthepaddingcontainsacountof
///howmanypaddingoctetsshouldbeignored,includingitself.Paddingmaybeneededby
///someencryptionalgorithmswithfixedblocksizesorforcarryingseveralRTPpacketsina
///lower-layerprotocoldataunit.
///</summary>
publicintPadding{get{return0;}}
///<summary>
///extension(X):1bit
///如果设定了extension位,定长头字段后面会有一个头扩展。
///Iftheextensionbitisset,thefixedheadermustbefollowedbyexactlyoneheaderextensio.
///</summary>
publicintExtension{get{return0;}}
///<summary>
///CSRCcount(CC):4bits
///CSRCcount标识了定长头字段中包含的CSRCidentifier的数量。
///TheCSRCcountcontainsthenumberofCSRCidentifiersthatfollowthefixedheader.
///</summary>
publicintCC{get{return0;}}
///<summary>
///marker(M):1bit
///marker是由一个profile定义的。用来允许标识在像报文流中界定帧界等的事件。
///一个profile可能定义了附加的标识位或者通过修改payloadtype域中的位数量来指定没有标识位.
///Theinterpretationofthemarkerisdefinedbyaprofile.Itisintendedtoallowsignificant
///eventssuchasframeboundariestobemarkedinthepacketstream.Aprofilemaydefine
///additionalmarkerbitsorspecifythatthereisnomarkerbitbychangingthenumberofbits
///inthepayloadtypefield.
///</summary>
publicintMarker{get{return0;}}
///<summary>
///payloadtype(PT):7bits
///这个字段定一个RTPpayload的格式和在应用中定义解释。
///profile可能指定一个从payloadtype码字到payloadformat的默认静态映射。
///也可以通过non-RTP方法来定义附加的payloadtype码字(见第3章)。
///在RFC3551[1]中定义了一系列的默认音视频映射。
///一个RTP源有可能在会话中改变payloadtype,但是这个域在复用独立的媒体时是不同的。(见5.2节)。
///接收者必须忽略它不识别的payloadtype。
///ThisfieldidentifiestheformatoftheRTPpayloadanddeterminesitsinterpretationbythe
///application.Aprofilemayspecifyadefaultstaticmappingofpayloadtypecodestopayload
///formats.Additionalpayloadtypecodesmaybedefineddynamicallythroughnon-RTPmeans
///(seeSection3).Asetofdefaultmappingsforaudioandvideoisspecifiedinthecompanion
///RFC3551[1].AnRTPsourcemaychangethepayloadtypeduringasession,butthisfield
///shouldnotbeusedformultiplexingseparatemediastreams(seeSection5.2).
///Areceivermustignorepacketswithpayloadtypesthatitdoesnotunderstand.
///</summary>
publicRtpPayloadTypePayloadType{get;privateset;}
///<summary>
///sequencenumber:16bits
///每发送一个RTP数据报文序列号值加一,接收者也可用来检测丢失的包或者重建报文序列。
///初始的值是随机的,这样就使得known-plaintext攻击更加困难,即使源并没有加密(见9。1),
///因为要通过的translator会做这些事情。关于选择随机数方面的技术见[17]。
///ThesequencenumberincrementsbyoneforeachRTPdatapacketsent,andmaybeused
///bythereceivertodetectpacketlossandtorestorepacketsequence.Theinitialvalueofthe
///sequencenumbershouldberandom(unpredictable)tomakeknown-plaintextattackson
///encryptionmoredificult,evenifthesourceitselfdoesnotencryptaccordingtothemethod
///inSection9.1,becausethepacketsmayflowthroughatranslatorthatdoes.Techniquesfor
///choosingunpredictablenumbersarediscussedin[17].
///</summary>
publicintSequenceNumber{get;privateset;}
///<summary>
///timestamp:32bits
///timestamp反映的是RTP数据报文中的第一个字段的采样时刻的时间瞬时值。
///采样时间值必须是从恒定的和线性的时间中得到以便于同步和jitter计算(见第6.4.1节)。
///必须保证同步和测量保温jitter到来所需要的时间精度(一帧一个tick一般情况下是不够的)。
///时钟频率是与payload所携带的数据格式有关的,在profile中静态的定义或是在定义格式的payloadformat中,
///或通过non-RTP方法所定义的payloadformat中动态的定义。如果RTP报文周期的生成,就采用虚拟的(nominal)
///采样时钟而不是从系统时钟读数。例如,在固定比特率的音频中,timestamp时钟会在每个采样周期时加一。
///如果音频应用中从输入设备中读入160个采样周期的块,thetimestamp就会每一块增加160,
///而不管块是否传输了或是丢弃了。
///对于序列号来说,timestamp初始值是随机的。只要它们是同时(逻辑上)同时生成的,
///这些连续的的RTP报文就会有相同的timestamp,
///例如,同属一个视频帧。正像在MPEG中内插视频帧一样,
///连续的但不是按顺序发送的RTP报文可能含有相同的timestamp。
///ThetimestampreflectsthesamplinginstantofthefirstoctetintheRTPdatapacket.The
///samplinginstantmustbederivedfromaclockthatincrementsmonotonicallyandlinearly
///intimetoallowsynchronizationandjittercalculations(seeSection6.4.1).Theresolution
///oftheclockmustbesuficientforthedesiredsynchronizationaccuracyandformeasuring
///packetarrivaljitter(onetickpervideoframeistypicallynotsuficient).Theclockfrequency
///isdependentontheformatofdatacarriedaspayloadandisspecifiedstaticallyintheprofile
///orpayloadformatspecificationthatdefinestheformat,ormaybespecifieddynamicallyfor
///payloadformatsdefinedthroughnon-RTPmeans.IfRTPpacketsaregeneratedperiodically,
///thenominalsamplinginstantasdeterminedfromthesamplingclockistobeused,nota
///readingofthesystemclock.Asanexample,forfixed-rateaudiothetimestampclockwould
///likelyincrementbyoneforeachsamplingperiod.Ifanaudioapplicationreadsblockscovering
///160samplingperiodsfromtheinputdevice,thetimestampwouldbeincreasedby160for
///eachsuchblock,regardlessofwhethertheblockistransmittedinapacketordroppedassilent.
///</summary>
publiclongTimestamp{get;privateset;}
///<summary>
///SSRC:32bits
///SSRC域识别同步源。为了防止在一个会话中有相同的同步源有相同的SSRCidentifier,
///这个identifier必须随机选取。
///生成随机identifier的算法见目录A.6。虽然选择相同的identifier概率很小,
///但是所有的RTPimplementation必须检测和解决冲突。
///第8章描述了冲突的概率和解决机制和RTP级的检测机制,根据唯一的SSRCidentifier前向循环。
///如果有源改变了它的源传输地址,
///就必须为它选择一个新的SSRCidentifier来避免被识别为循环过的源(见第8.2节)。
///TheSSRCfieldidentifiesthesynchronizationsource.Thisidentifiershouldbechosen
///randomly,withtheintentthatnotwosynchronizationsourceswithinthesameRTPsession
///willhavethesameSSRCidentifier.Anexamplealgorithmforgeneratingarandomidentifier
///ispresentedinAppendixA.6.Althoughtheprobabilityofmultiplesourceschoosingthesame
///identifierislow,allRTPimplementationsmustbepreparedtodetectandresolvecollisions.
///Section8describestheprobabilityofcollisionalongwithamechanismforresolvingcollisions
///anddetectingRTP-levelforwardingloopsbasedontheuniquenessoftheSSRCidentifier.If
///asourcechangesitssourcetransportaddress,itmustalsochooseanewSSRCidentifierto
///avoidbeinginterpretedasaloopedsource(seeSection8.2).
///</summary>
publicintSSRC{get{return0;}}
///<summary>
///每一个RTP包中都有前12个字节定长的头字段
///ThefirsttwelveoctetsarepresentineveryRTPpacket
///</summary>
publicconstintHeaderSize=12;
///<summary>
///RTP消息头
///</summary>
privatebyte[]_header;
///<summary>
///RTP消息头
///</summary>
publicbyte[]Header{get{return_header;}}
///<summary>
///RTP有效载荷长度
///</summary>
privateint_payloadSize;
///<summary>
///RTP有效载荷长度
///</summary>
publicintPayloadSize{get{return_payloadSize;}}
///<summary>
///RTP有效载荷
///</summary>
privatebyte[]_payload;
///<summary>
///RTP有效载荷
///</summary>
publicbyte[]Payload{get{return_payload;}}
///<summary>
///RTP消息总长度,包括Header和Payload
///</summary>
publicintLength{get{returnHeaderSize+PayloadSize;}}
///<summary>
///RTP(RFC3550)协议数据包
///</summary>
///<paramname="playloadType">数据报文有效载荷类型</param>
///<paramname="sequenceNumber">数据报文序列号值</param>
///<paramname="timestamp">数据报文采样时刻</param>
///<paramname="data">数据</param>
///<paramname="dataSize">数据长度</param>
publicRtpPacket(
RtpPayloadTypeplayloadType,
intsequenceNumber,
longtimestamp,
byte[]data,
intdataSize)
{
//fillchangingheaderfields
SequenceNumber=sequenceNumber;
Timestamp=timestamp;
PayloadType=playloadType;
//buildtheheaderbistream
_header=newbyte[HeaderSize];
//filltheheaderarrayofbytewithRTPheaderfields
_header[0]=(byte)((Version<<6)|(Padding<<5)|(Extension<<4)|CC);
_header[1]=(byte)((Marker<<7)|(int)PayloadType);
_header[2]=(byte)(SequenceNumber>>8);
_header[3]=(byte)(SequenceNumber);
for(inti=0;i<4;i++)
{
_header[7-i]=(byte)(Timestamp>>(8*i));
}
for(inti=0;i<4;i++)
{
_header[11-i]=(byte)(SSRC>>(8*i));
}
//fillthepayloadbitstream
_payload=newbyte[dataSize];
_payloadSize=dataSize;
//fillpayloadarrayofbytefromdata(giveninparameteroftheconstructor)
Array.Copy(data,0,_payload,0,dataSize);
}
///<summary>
///RTP(RFC3550)协议数据包
///</summary>
///<paramname="playloadType">数据报文有效载荷类型</param>
///<paramname="sequenceNumber">数据报文序列号值</param>
///<paramname="timestamp">数据报文采样时刻</param>
///<paramname="frame">图片</param>
publicRtpPacket(
RtpPayloadTypeplayloadType,
intsequenceNumber,
longtimestamp,
Imageframe)
{
//fillchangingheaderfields
SequenceNumber=sequenceNumber;
Timestamp=timestamp;
PayloadType=playloadType;
//buildtheheaderbistream
_header=newbyte[HeaderSize];
//filltheheaderarrayofbytewithRTPheaderfields
_header[0]=(byte)((Version<<6)|(Padding<<5)|(Extension<<4)|CC);
_header[1]=(byte)((Marker<<7)|(int)PayloadType);
_header[2]=(byte)(SequenceNumber>>8);
_header[3]=(byte)(SequenceNumber);
for(inti=0;i<4;i++)
{
_header[7-i]=(byte)(Timestamp>>(8*i));
}
for(inti=0;i<4;i++)
{
_header[11-i]=(byte)(SSRC>>(8*i));
}
//fillthepayloadbitstream
using(MemoryStreamms=newMemoryStream())
{
frame.Save(ms,ImageFormat.Jpeg);
_payload=ms.ToArray();
_payloadSize=_payload.Length;
}
}
///<summary>
///RTP(RFC3550)协议数据包
///</summary>
///<paramname="packet">数据包</param>
///<paramname="packetSize">数据包长度</param>
publicRtpPacket(byte[]packet,intpacketSize)
{
//checkiftotalpacketsizeislowerthantheheadersize
if(packetSize>=HeaderSize)
{
//gettheheaderbitsream
_header=newbyte[HeaderSize];
for(inti=0;i<HeaderSize;i++)
{
_header[i]=packet[i];
}
//getthepayloadbitstream
_payloadSize=packetSize-HeaderSize;
_payload=newbyte[_payloadSize];
for(inti=HeaderSize;i<packetSize;i++)
{
_payload[i-HeaderSize]=packet[i];
}
//interpretthechangingfieldsoftheheader
PayloadType=(RtpPayloadType)(_header[1]&127);
SequenceNumber=UnsignedInt(_header[3])+256*UnsignedInt(_header[2]);
Timestamp=UnsignedInt(_header[7])
+256*UnsignedInt(_header[6])
+65536*UnsignedInt(_header[5])
+16777216*UnsignedInt(_header[4]);
}
}
///<summary>
///将消息转换成byte数组
///</summary>
///<returns>消息byte数组</returns>
publicbyte[]ToArray()
{
byte[]packet=newbyte[Length];
Array.Copy(_header,0,packet,0,HeaderSize);
Array.Copy(_payload,0,packet,HeaderSize,PayloadSize);
returnpacket;
}
///<summary>
///将消息体转换成图片
///</summary>
///<returns>图片</returns>
publicBitmapToBitmap()
{
returnnewBitmap(newMemoryStream(_payload));
}
///<summary>
///将消息体转换成图片
///</summary>
///<returns>图片</returns>
publicImageToImage()
{
returnImage.FromStream(newMemoryStream(_payload));
}
///<summary>
///将图片转换成消息
///</summary>
///<paramname="playloadType">数据报文有效载荷类型</param>
///<paramname="sequenceNumber">数据报文序列号值</param>
///<paramname="timestamp">数据报文采样时刻</param>
///<paramname="frame">图片帧</param>
///<returns>
///RTP消息
///</returns>
publicstaticRtpPacketFromImage(
RtpPayloadTypeplayloadType,
intsequenceNumber,
longtimestamp,
Imageframe)
{
returnnewRtpPacket(playloadType,sequenceNumber,timestamp,frame);
}
///<summary>
///returntheunsignedvalueof8-bitintegernb
///</summary>
///<paramname="nb"></param>
///<returns></returns>
privatestaticintUnsignedInt(intnb)
{
if(nb>=0)
return(nb);
else
return(256+nb);
}
}
相关文章
- c# 字符串转时间的方式
- C#winForm窗体美化
- C#-GDI+中发生一般性错误的解决办法
- dotnet6 C# 一个国内还能用的 NTP 时间校准客户端的实现
- C#邮件发送和接收实现代码
- 用C#Winform做出全透明的磨砂玻璃窗体效果代码
- c#命名空间和程序集
- C#调用和实现WebService,纯手工打造!
- asp.netC#实现下载文件的六种方法实例
- C#中日期时间的简单操作
- IE6下javasc#ipt:void(0)无效的解决方法
- c#使用简单工厂模式实现生成html文件的封装类分享
- c#实现把异常写入日志示例(异常日志)
- C#实现软件监控外部程序运行状态的方法
- 基于C#实现的仿windows左侧伸缩菜单效果
- C#使用晚绑定来实现压缩Access数据库的方法
- C#实现自定义定时组件的方法
- C#使用yield关键字让自定义集合实现foreach遍历的方法
- 使用C#实现在word中插入页眉页脚的方法
- C#生成互不相同随机数的实现方法
- C#实现生成mac地址与IP地址注册码的两种方法
- C#实现改变DataGrid某一行和单元格颜色的方法
- C#画图之饼图折线图的实现方法