zl程序教程

您现在的位置是:首页 >  后端

当前栏目

使用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);
    }
  }