一、RTP协议

RTP包由RTP头部和RTP荷载构成: RTP Packet = RTP Header + RTP payload.

1.1 RTP头部(RTP Header)

ConverterFactories rstp流转换 rstp 推流_UDP

版本号(V):2Bit,用来标志使用RTP版本

填充位§:1Bit,如果该位置位,则该RTP包的尾部就包含填充的附加字节

扩展位(X):1Bit,如果该位置位,则该RTP包的固定头部后面就跟着一个扩展头部

CSRC技术器(CC):4Bit,含有固定头部后面跟着的CSRC的数据

标记位(M):1Bit,该位的解释由配置文档来承担

载荷类型(PT):7Bit,标识了RTP载荷的类型

序列号(SN):16Bit,发送方在每发送完一个RTP包后就将该域的值增加1,可以由该域检测包的丢失及恢复

包的序列。序列号的初始值是随机的

时间戳(timestamp):32比特,记录了该包中数据的第一个字节的采样时刻, H264/HEVC统一采用90kHz采样时钟,如果使用帧率fps来设置时间戳,则递增数值为90000/fps。

同步源标识符(SSRC):32比特,同步源就是RTP包源的来源。在同一个RTP会话中不能有两个相同的SSRC值

贡献源列表(CSRC List):0-15项,每项32比特,这个不常用

1.2 RTP荷载(RTP Payload)

RTP Payload结构一般分为3种:

  • 单NALU分组(Single NAL Unit Packet): 一个分组只包含一个NALU。
  • 聚合分组(Aggregation Packet): 一个分组包含多个NALU。
  • 分片分组(Fragmentation Unit):一个比较长的NALU分在多个RTP包中。

二、H.264的RTP打包

2.1 H.264格式

H.264由一个一个的NALU组成,每个NALU之间使用00 00 00 0100 00 01分隔开

每个NALU的第一次字节都有特殊的含义,其内容如下


描述

bit[7]

必须为0

bit[5-6]

标记该NALU的重要性

bit[0-4]

NALU单元的类型

好,对于H.264格式了解这么多就够了,我们的目的是想从一个H.264的文件中将一个一个的NALU提取出来,然后封装成RTP包,下面介绍如何将NALU封装成RTP包

2.2 H.264的RTP打包方式

H.264可以由三种RTP打包方式

  • 单NALU打包
    一个RTP包包含一个完整的NALU
  • 聚合打包
    对于较小的NALU,一个RTP包可包含多个完整的NALU
  • 分片打包
    对于较大的NALU,一个NALU可以分为多个RTP包发送

注意:这里要区分好概念,每一个RTP包都包含一个RTP头部和RTP荷载,这是固定的。而H.264发送数据可支持三种RTP打包方式

比较常用的是单NALU打包分片打包,本文也只介绍这两种

单NALU打包

所谓单NALU打包就是将一整个NALU的数据放入RTP包的载荷中

这是最简单的一种方式,无需过多的讲解

分片打包

每个RTP包都有大小限制的,因为RTP一般都是使用UDP发送,UDP没有流量控制,所以要限制每一次发送的大小,所以如果一个NALU的太大,就需要分成多个RTP包发送,如何分成多个RTP包,下面来好好讲一讲

首先要明确,RTP包的格式是绝不会变的,永远多是RTP头+RTP载荷

ConverterFactories rstp流转换 rstp 推流_UDP_02

RTP头部是固定的,那么只能在RTP载荷中去添加额外信息来说明这个RTP包是表示同一个NALU

如果是分片打包的话,那么在RTP载荷开始有两个字节的信息,然后再是NALU的内容

ConverterFactories rstp流转换 rstp 推流_UDP_03

  • 第一个字节位FU Indicator,其格式如下
  • ConverterFactories rstp流转换 rstp 推流_UDP_04

  • 高三位:与NALU第一个字节的高三位相同
    Type:28,表示该RTP包一个分片,为什么是28?因为H.264的规范中定义的,此外还有许多其他Type,这里不详讲
  • 第二个字节位FU Header,其格式如下

S:标记该分片打包的第一个RTP包

E:比较该分片打包的最后一个RTP包

Type:NALU的Type

2.3 H.264 RTP打包的sdp描述

sdp文件有什么用?

sdp描述着媒体信息,当使用vlc打开这个sdp文件后,会根据这些信息做相应的操作(创建套接字…),然后等待接收RTP包

这里给出RTP打包H.264的sdp文件,并描述每一行是什么意思

m=video 9832 RTP/AVP 96 
a=rtpmap:96 H264/90000
a=framerate:25
c=IN IP4 127.0.0.1

这个一个媒体级的sdp描述,关于sdp文件描述详情可看从零开始写一个RTSP服务器(一)不一样的RTSP协议讲解

  • m=video 9832 RTP/AVP 96
    格式为 m=<媒体类型> <端口号> <传输协议> <媒体格式 >
    媒体类型:video,表示这是一个视频流
    端口号:9832,表示UDP发送的目的端口为9832
    传输协议:RTP/AVP,表示RTP OVER UDP,通过UDP发送RTP包
    媒体格式:表示负载类型(payload type),一般使用96表示H.264
  • a=rtpmap:96 H264/90000
    格式为a=rtpmap:<媒体格式><编码格式>/<时钟频率>
  • a=framerate:25
    表示帧率
  • c=IN IP4 127.0.0.1
    IN:表示internet
    IP4:表示IPV4
    127.0.0.1:表示UDP发送的目的地址为127.0.0.1

特别注意:这段sdp文件描述的udp发送的目的IP为127.0.0.1,目的端口为9832

type

type value

vps

32

sps

33

pps

34

SEI

39

IDR

19或者20

常用P帧,后置图像帧

1

H264 的RTP打包代码

int rtpSendH264Frame(int socket, const char* ip, int16_t port,
                            struct RtpPacket* rtpPacket, uint8_t* frame, uint32_t frameSize)
{
    uint8_t naluType; // nalu第一个字节
    int sendBytes = 0;
    int ret;

    naluType = frame[0];

    if (frameSize <= RTP_MAX_PKT_SIZE) // nalu长度小于最大包场:单一NALU单元模式
    {
        /*
         *   0 1 2 3 4 5 6 7 8 9
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |F|NRI|  Type   | a single NAL unit ... |
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         */
        memcpy(rtpPacket->payload, frame, frameSize);
        ret = rtpSendPacket(socket, ip, port, rtpPacket, frameSize);
        if(ret < 0)
            return -1;

        rtpPacket->rtpHeader.seq++;
        sendBytes += ret;
        if ((naluType & 0x1F) == 7 || (naluType & 0x1F) == 8) // 如果是SPS、PPS就不需要加时间戳
            goto out;
    }
    else // nalu长度小于最大包场:分片模式
    {
        /*
         *  0                   1                   2
         *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
         * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         * | FU indicator  |   FU header   |   FU payload   ...  |
         * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         */

        /*
         *     FU Indicator
         *    0 1 2 3 4 5 6 7
         *   +-+-+-+-+-+-+-+-+
         *   |F|NRI|  Type   |
         *   +---------------+
         */

        /*
         *      FU Header
         *    0 1 2 3 4 5 6 7
         *   +-+-+-+-+-+-+-+-+
         *   |S|E|R|  Type   |
         *   +---------------+
         */

        int pktNum = frameSize / RTP_MAX_PKT_SIZE;       // 有几个完整的包
        int remainPktSize = frameSize % RTP_MAX_PKT_SIZE; // 剩余不完整包的大小
        int i, pos = 1;

        /* 发送完整的包 */
        for (i = 0; i < pktNum; i++)
        {
            rtpPacket->payload[0] = (naluType & 0x60) | 28;
            rtpPacket->payload[1] = naluType & 0x1F;

            if (i == 0) //第一包数据
                rtpPacket->payload[1] |= 0x80; // start
            else if (remainPktSize == 0 && i == pktNum - 1) //最后一包数据
                rtpPacket->payload[1] |= 0x40; // end

            memcpy(rtpPacket->payload+2, frame+pos, RTP_MAX_PKT_SIZE);
            ret = rtpSendPacket(socket, ip, port, rtpPacket, RTP_MAX_PKT_SIZE+2);
            if(ret < 0)
                return -1;

            rtpPacket->rtpHeader.seq++;
            sendBytes += ret;
            pos += RTP_MAX_PKT_SIZE;
        }

        /* 发送剩余的数据 */
        if (remainPktSize > 0)
        {
            rtpPacket->payload[0] = (naluType & 0x60) | 28;
            rtpPacket->payload[1] = naluType & 0x1F;
            rtpPacket->payload[1] |= 0x40; //end

            memcpy(rtpPacket->payload+2, frame+pos, remainPktSize+2);
            ret = rtpSendPacket(socket, ip, port, rtpPacket, remainPktSize+2);
            if(ret < 0)
                return -1;

            rtpPacket->rtpHeader.seq++;
            sendBytes += ret;
        }
    }

out:

    return sendBytes;
}
static void rtpSendNAL(RTPMuxContext *ctx, const uint8_t *nal, int size, int last){
    printf("rtpSendNAL  len = %d M=%d\n", size, last);

    // Single NAL Packet or Aggregation Packets
    if (size <= RTP_PAYLOAD_MAX){

        // Aggregation Packets
        if (ctx->aggregation){
            /*
             *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             *  |STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR & Data | NALU 2 Size | NALU 2 HDR & Data | ... |
             *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             *
             * */
            int buffered_size = (int)(ctx->buf_ptr - ctx->buf);  // size of data in ctx->buf
            uint8_t curNRI = (uint8_t)(nal[0] & 0x60);           // NAL NRI

            // The remaining space in ctx->buf is less than the required space
            if (buffered_size + 2 + size > RTP_PAYLOAD_MAX) {
                rtpSendData(ctx, ctx->buf, buffered_size, 0);
                buffered_size = 0;
            }

            /*
             *    STAP-A/AP NAL Header
             *     +---------------+
             *     |0|1|2|3|4|5|6|7|
             *     +-+-+-+-+-+-+-+-+
             *     |F|NRI|  Type   |
             *     +---------------+
             * */
            if (buffered_size == 0){
                *ctx->buf_ptr++ = (uint8_t)(24 | curNRI);  // 0x18
            } else {
                uint8_t lastNRI = (uint8_t)(ctx->buf[0] & 0x60);
                if (curNRI > lastNRI){  // if curNRI > lastNRI, use new curNRI
                    ctx->buf[0] = (uint8_t)((ctx->buf[0] & 0x9F) | curNRI);
                }
            }

            // set STAP-A/AP NAL Header F = 1, if this NAL F is 1.
            ctx->buf[0] |= (nal[0] & 0x80);

            // NALU Size + NALU Header + NALU Data
            Load16(ctx->buf_ptr, (uint16_t)size);   // NAL size
            ctx->buf_ptr += 2;
            memcpy(ctx->buf_ptr, nal, size);        // NALU Header & Data
            ctx->buf_ptr += size;

            // meet last NAL, send all buf
            if (last == 1){
                rtpSendData(ctx, ctx->buf, (int)(ctx->buf_ptr - ctx->buf), 1);
            }
        }
        // Single NAL Unit RTP Packet
        else {
            /*
             *   0 1 2 3 4 5 6 7 8 9
             *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             *  |F|NRI|  Type   | a single NAL unit ... |
             *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             * */
            rtpSendData(ctx, nal, size, last);
        }

    } else {  // 分片分组
        /*
         *
         *  0                   1                   2
         *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
         * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         * | FU indicator  |   FU header   |   FU payload   ...  |
         * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *
         * */
        if (ctx->buf_ptr > ctx->buf){
            rtpSendData(ctx, ctx->buf, (int)(ctx->buf_ptr - ctx->buf), 0);
        }

        int headerSize;
        uint8_t *buff = ctx->buf;
        uint8_t type = nal[0] & 0x1F;
        uint8_t nri = nal[0] & 0x60;

        /*
         *     FU Indicator
         *    0 1 2 3 4 5 6 7
         *   +-+-+-+-+-+-+-+-+
         *   |F|NRI|  Type   |
         *   +---------------+
         * */
        buff[0] = 28;   // FU Indicator; FU-A Type = 28
        buff[0] |= nri;

        /*
         *      FU Header
         *    0 1 2 3 4 5 6 7
         *   +-+-+-+-+-+-+-+-+
         *   |S|E|R|  Type   |
         *   +---------------+
         * */
        buff[1] = type;     // FU Header uses NALU Header
        buff[1] |= 1 << 7;  // S(tart) = 1
        headerSize = 2;
        size -= 1;
        nal += 1;

        while (size + headerSize > RTP_PAYLOAD_MAX) {
            memcpy(&buff[headerSize], nal, (size_t)(RTP_PAYLOAD_MAX - headerSize));
            rtpSendData(ctx, buff, RTP_PAYLOAD_MAX, 0);
            nal += RTP_PAYLOAD_MAX - headerSize;
            size -= RTP_PAYLOAD_MAX - headerSize;
            buff[1] &= 0x7f;  // buff[1] & 0111111, S(tart) = 0
        }
        buff[1] |= 0x40;      // buff[1] | 01000000, E(nd) = 1
        memcpy(&buff[headerSize], nal, size);
        rtpSendData(ctx, buff, size + headerSize, last);
    }
}

三. H.265 RTP 打包原理

如果NALU对应的Slice为一帧的开始,则用4字节表示,即0x00 00 00 01;否则用3字节表示,0x00 00 01。下面都以4个字节为一帧来描述。 RTP打包就是前四个字节0x00 00 00 01去掉,添加上PayloadHdr,然后发送出去。

3.1 H.265 RTP打包格式

单个NAL包:Single NAL unit packet

0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           PayloadHdr          |      DONL (conditional)       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   |                  NAL unit payload data                        |
   |                                                               |
   |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                               :...OPTIONAL RTP padding        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

在SDP中sprop-max-don-diff = 0时,DONL可以省略;当 0<sprop-max-don-diff<=32767时,DONL不能省略。

去掉原始码流中的“00 00 00 01”,在头部添加tcp(12 bits + 2bits(length))或者udp(12bits)的头,然后发送出去。

分片单元:Fragmentation unit (FU)

1.The structure of an FU

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |    PayloadHdr (Type=49)       |   FU header   | DONL (cond)   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
   | DONL (cond)   |                                               |
   |-+-+-+-+-+-+-+-+                                               |
   |                         FU payload                            |
   |                                                               |
   |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                               :...OPTIONAL RTP padding        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

2.H.265 PayloadHdr
---------------------------------------
|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
---------------------------------------
|F|   Type      |  LayerId  |Tid|
     
                 
3.The structure of FU header 
  +---------------+
  |0|1|2|3|4|5|6|7|
  +-+-+-+-+-+-+-+-+
  |S|E|  FuType   |
  +---------------+
*       S       = variable
*       E       = variable
*       FuType  = NAL unit type
*       第一個包:	 S=1,E=0;
*       中間包:      S=0,E=0
*       最後一包:    S=0,E=1

单个NAL包是指size <1500-RTP_HEADER_LENGTH - bits(一般情况为1500,有些可能更小是1300)时,采用单个NAL包发送。当大于这个值时候通常采用FU的发送方式,RTP切片,分为首包,中间包,尾包数据。

将PayloadHdr中type设置为49,设置起始位置S和结束位置E

3.2 H.265 RTP打包的sdp描述

v=0
o=- 0 0 IN IP4 192.168.1.77
s=No Name
c=IN IP4 192.168.1.77
t=0 0
a=tool:libavformat 58.12.100
m=video 1234 RTP/AVP 96
a=rtpmap:96 H265/90000
a=fmtp:96 sprop-vps=QAEMAf//AWAAAAMAsAAAAwAAAwB7rAk=; sprop-sps=QgEBAWAAAAMAsAAAAwAAAwB7oAPAgBDlja5JFL03AQEBAIA=; sprop-pps=RAHA8s5wOzQA

H265的RTP程序代码

int rtpSendH265Frame(int socket, const char* ip, int16_t port,
                            struct RtpPacket* rtpPacket, uint8_t* frame, uint32_t frameSize)
{
    int naluType; // nalu第一个字节
    int sendBytes = 0;
    int ret;

    naluType = (frame[0]>>1) & 0x3F;

    if (frameSize <= RTP_MAX_PKT_SIZE) // nalu长度小于最大包场:单一NALU单元模式
    {
      /*
         *  0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         *  |F|    Type   |  LayerId  |TID|a single NAL unit ...
         *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         */
        memcpy(rtpPacket->payload, frame, frameSize);
        ret = rtpSendPacket(socket, ip, port, rtpPacket, frameSize);
        if(ret < 0)
            return -1;

        rtpPacket->rtpHeader.seq++;
        sendBytes += ret;

        if (((naluType & 0x7F)>>1) == 32 || ((naluType & 0x7F)>>1) == 33) // 如果是SPS、PPS就不需要加时间戳
            goto out;
    }
    else // nalu长度小于最大包场:分片模式
    {   /*
          create the HEVC payload header and transmit the buffer as fragmentation units (FU)

                 0                   1
                 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
                 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                 |F|   Type    |  LayerId  | TID |
                 +-------------+-----------------+

                 F       = 0
                 Type    = 49 (fragmentation unit (FU))
                 LayerId = 0
                 TID     = 1
       */


        /*
         *      FU Header
         *    0 1 2 3 4 5 6 7
         *   +-+-+-+-+-+-+-+-+
         *   |S|E|R|  Type   |
         *   +---------------+
         */

        int pktNum = frameSize / RTP_MAX_PKT_SIZE;       // 有几个完整的包
        int remainPktSize = frameSize % RTP_MAX_PKT_SIZE; // 剩余不完整包的大小
        int i, pos = 2;

        /* 发送完整的包 */
        for (i = 0; i < pktNum; i++)
        {
            rtpPacket->payload[0] = 49<<1;
            rtpPacket->payload[1] = 1;
            rtpPacket->payload[2] = naluType;

            //rtpPacket->payload[2] |= 1<<7;

            if (i == 0) //第一包数据
                rtpPacket->payload[2] |= 1<<7;// start
            else if (remainPktSize == 0 && i == pktNum - 1) //最后一包数据
                rtpPacket->payload[2] &= ~(1 << 7); // end

            memcpy(rtpPacket->payload+3, frame+pos, RTP_MAX_PKT_SIZE);
            ret = rtpSendPacket(socket, ip, port, rtpPacket, RTP_MAX_PKT_SIZE+3);
            if(ret < 0)
                return -1;

            rtpPacket->rtpHeader.seq++;
            sendBytes += ret;
            pos += RTP_MAX_PKT_SIZE;
        }

        /* 发送剩余的数据 */
        if (remainPktSize > 0)
        {
            rtpPacket->payload[0] = 49<<1;
            rtpPacket->payload[1] = 1;
            rtpPacket->payload[2] = naluType;
            rtpPacket->payload[2] |= 1<<6; //end

            memcpy(rtpPacket->payload+3, frame+pos, remainPktSize+3);
            ret = rtpSendPacket(socket, ip, port, rtpPacket, remainPktSize+3);
            if(ret < 0)
                return -1;

            rtpPacket->rtpHeader.seq++;
            sendBytes += ret;
        }
    }

out:

    return sendBytes;
}
static void rtpSendNAL(RTPMuxContext *ctx, const uint8_t *buf, int size, int last)
{
    //RTPMuxContext *rtp_ctx = ctx->priv_data;
    int rtp_payload_size   = RTP_PAYLOAD_MAX - 3;
    int nal_type           = (buf[0] >> 1) & 0x3F;

    /* send it as one single NAL unit? */
    if (size <= RTP_PAYLOAD_MAX) {
     /* use the original NAL unit buffer and transmit it as RTP payload */
     rtpSendData(ctx, buf, size, last);
    } else {
      /*
          create the HEVC payload header and transmit the buffer as fragmentation units (FU)

                 0                   1
                 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
                 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                 |F|   Type    |  LayerId  | TID |
                 +-------------+-----------------+

                 F       = 0
                 Type    = 49 (fragmentation unit (FU))
                 LayerId = 0
                 TID     = 1
       */
        ctx->buf[0] = 49 << 1;
        ctx->buf[1] = 1;

       /*
             create the FU header

                  0 1 2 3 4 5 6 7
                  +-+-+-+-+-+-+-+-+
                  |S|E|  FuType   |
                  +---------------+

                  S       = variable
                  E       = variable
                  FuType  = NAL unit type
            */
         ctx->buf[2]  = nal_type;
             /* set the S bit: mark as start fragment */
         ctx->buf[2] |= 1 << 7;

            /* pass the original NAL header */
          buf += 2;
          size -= 2;

         while (size+3 > rtp_payload_size) {
             /* complete and send current RTP packet */
            memcpy(&ctx->buf[3], buf, rtp_payload_size);
            rtpSendData(ctx, ctx->buf, RTP_PAYLOAD_MAX, 0);

            buf += rtp_payload_size;
            size -= rtp_payload_size;

                /* reset the S bit */
             ctx->buf[2] &= ~(1 << 7);
           }

            /* set the E bit: mark as last fragment */
             ctx->buf[2] |= 1 << 6;

             /* complete and send last RTP packet */
           memcpy(&ctx->buf[3], buf, size);
           rtpSendData(ctx, ctx->buf, size + 3, last);
       }
}