06-----rtp解包成aac时,对应的重复包,乱序,丢包是如何处理的(包括C++和C语言的思路)

本文详细介绍了在处理RTP解包成AAC时,如何应对重复包、乱序和丢包的情况。在C语言的实现中,通过检查序列号和时间戳来判断并处理这些问题。C++的实现则利用map容器简化了处理流程。文章提供了具体的代码示例,帮助读者理解这两种语言在处理网络流媒体数据时的不同策略。
摘要由CSDN通过智能技术生成

对于从事于流媒体开发的朋友来说,经常需要对接各种协议的处理。下面我们主要讲一下rtp解包成aac时,对应的重复包,乱序,丢包是如何处理的。

一 C语言的思路

并不是说这个思路不能用于C++,只不过C++有其它更好的方法,而这种思路更适合更方便C语言进行处理。

1 本项目对应的重复包,乱序,丢包的思路是如何处理的:

  • 1)重复包:unpack文件中解rtp包时看到,只要有seq不相等,那么就会为该帧记录丢包的标志,把该帧丢弃,即使后面可能收到完整的一帧,实际上是不可能的,因为udp是不会重传的。
  • 2)乱序:unpack解rtp包时看到,只要有seq不相等,那么就会为该帧记录丢包的标志,把该帧丢弃,乱序是仍可能收到完整一帧的,不过这里忽略这种情况进行处理了。
  • 3)丢包:直接记录该帧丢包,丢弃该帧即可。

2 代码解释:
每一次从网络中拿到数据并且反序列化(大端转小端)后,都会调rtp_payload_check对重复包,乱序,丢包进行检查。
在这里插入图片描述

/**
  * 参1:实际就是对应的解码结构体,例如类似h264解码时的rtp_decode_h264_t,
  *         用于缓存一帧数据,内部的seq与timestamp一直递增,一直保存上一个rtp包的seq与timestamp。
  * 参2:反序列化后的临时数据,保存着本次rtp包的相关信息,内部只是简单的指针指向。
  *
  * 返回值:作用不大,这里没意义,可写成void代表不关心返回值。
*/
int rtp_payload_check(struct rtp_payload_helper_t* helper, const struct rtp_packet_t* pkt)
{
    // 1 检查第一个包是否丢包,整个程序有且只会进一次。因为只会在第一个包才会是-1,因为在ctx->decoder->create初始化为-1.
    // 这里的作用是:由于收到第一个包时,helper是没有上一包的数据,所以需要人为去赋值制造条件。
    // 赋值目的:1)使pkt->rtp.seq减1,目的为了让首包满足条件2,禁止它标注为丢包。
    //         2)pkt->rtp.timestamp加1,目的为了满足条件3,不让它相等,让其调用rtp_payload_onframe对上一帧的size和lost进行清0,防止影响下一帧的判断。
    //              实际上第一帧size和lost本来就是0,所以应该加不加都一样。试过了就是一样的,并且都能播放。
    if (-1 == helper->__flags)
    {
        // printf("首包,并且全程只会进来一次\n");
        helper->__flags = 0;                            // 在这里时:代表所有rtp的第一个包没有丢包。
        helper->seq = (uint16_t)(pkt->rtp.seq - 1);
        helper->timestamp = pkt->rtp.timestamp + 1;
    }

    // 2 检查序列号:本rtp包与上一个rtp包的seq + 1进行比较,若等说明没丢,否则丢包。
    int lost = 0;           // 本帧的丢包标志
    if ((uint16_t)pkt->rtp.seq != (uint16_t)(helper->seq + 1))
    {
        lost = 1;
        //helper->size = 0;
        helper->lost = 1;
        //helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST;
        //helper->timestamp = pkt->rtp.timestamp;
    }
    helper->seq = (uint16_t)pkt->rtp.seq;// 更新本包的seq为上一个rtp包的seq,为下一次比较做准备,本帧丢包也这样处理,因为已经记录lost=1了。

    // 3 检查timestamp。这里检查时间戳主要是为了:1)重置上一帧的lost与size数据情况。2)对首包丢包和尾包丢包的情况进行重新记录,防止1)误重置了该帧的丢包情况。
    // 注:只有在两帧交界处才会进入该if条件,即有这两种情况:正常两帧时间戳更替(包含了首包丢包情况)以及尾包丢包的情况。
    //       同一帧的rtp包是不会进入3的if,因为时间戳是一样的。
    // printf("++++++++++++check timestamp++++++++++++helper timestamp:%u, timestamp: %u\n", helper->timestamp, pkt->rtp.timestamp);
    if (pkt->rtp.timestamp != helper->timestamp)
    {
        // printf("check, helper->size:%d, helper->lost:%d\n", helper->size, helper->lost);
        rtp_payload_onframe(helper);        // 清除上一帧的内容,因为只有两帧交替时才会进来3的if。

        // 3.1 对if(0 != lost)的解释:
        // 原来英文对if(0 != lost)注释翻译:包丢失在时间戳改变时,不能知道丢失的包是在旧包的尾部还是在新包的开始,所以两个包标记为包丢失。
        // if(0 != lost)是因为:若上面2的seq条件不满足,则本包标记丢包,但是调用rtp_payload_onframe之后会清除了该标志,所以必须重新置本帧数据为丢包。
        // 1)例如假设第二帧的第一个包丢了,导致上一帧(描述上一包更准确,只不过这个例子称为上一帧也行)的seq+1!=本包,那么刚好又不是同一帧,
        // 所以会进入3的if条件。rtp_payload_onframe清掉后必须重置,表示该帧为丢过包的一帧。这个例子好理解,关键是尾包的例子。
        //
        // 2)假设第二帧的前两2个包没丢,第3个包(尾包,只有3个包)丢了的时候,由于上一包序列号seq+1!=本包seq,并且由于是尾包丢失,所以本包必定是新的一帧的包,
        // 故时间戳是不一样的,所以必定会进入3的if,也就是说if(0 != lost)这种情况,会发生在首包丢包或者尾包丢包的情况。
        // 不过首包丢还是尾包丢都不重要,因为都是本帧丢包而已。
        if(0 != lost)
            helper->lost = lost;
    }
    helper->timestamp = pkt->rtp.timestamp;// helper->timestamp只会保存上一个rtp的时间戳。当一帧分开多个rtp包发送时,实际上这两者的值是一样的。

    return 0;
}

rtp_payload_onframe函数实际不用看都行,只要理解上面就基本懂得如何处理重复包,乱序,丢包的了,这里给出是为了方便大家更容易理解。

/**
  * 缓存完整的一帧后,调用本函数进行处理,例如这里是写入文件。
  * 本函数实际有两个作用:
  * 1)从rtp_payload_check进入时:处理丢包的情况或者清除上一帧的数据。
  * 2)从pkt.rtp.m进入时:处理没丢包时的一帧数据或者上一帧丢包时的数据清理,但不会清掉RTP_PAYLOAD_FLAG_PACKET_LOST,
  *     因为上一帧丢包时,由于在rtp_payload_check检测到丢包,__flags被置为RTP_PAYLOAD_FLAG_PACKET_LOST,并且此时lost=1
  *     所以当到遇到m=1时,调用本函数,是不会进入packet处理包并清掉RTP_PAYLOAD_FLAG_PACKET_LOST的。
  *
  * 参1:缓存着aac完整一帧数据,
  *         并且保存着连续递增的seq(1开始按1递增)与timestamp(初始值0按1024递增)的结构体,因为helper始终保存上一个rtp包的seq、timestamp。
*/
int rtp_payload_onframe(struct rtp_payload_helper_t *helper)
{
    int r;
    r = 0;

    // 1 缓存完整一帧后,如果没有丢包,则发送已经缓存好的一帧。rtp_payload_check的时候不会进来if,因为下面return之前size置为0了。
    // printf("helper->size:%d, helper->lost:%d\n", helper->size, helper->lost);
    if (helper->size > 0
#if !defined(RTP_ENABLE_COURRUPT_PACKET)    // 该宏没有定义。但有啥用???
        && 0 == helper->lost                // 没有丢包才处理该帧
#endif
        )
    {
        // (这里是没有丢包的情况才会进来。)
        // 1.1 拿到完整一帧,调用户函数进行写入,packet是rtp_decode_packet用户解码函数。
        // 这里看到,一帧数据是从helper的->ptr中取出的。
        r = helper->handler.packet(helper->cbparam, helper->ptr, helper->size,
                                   helper->timestamp,           // 用户写入时并未用到时间戳
                                   helper->__flags | (helper->lost ? RTP_PAYLOAD_FLAG_PACKET_CORRUPT : 0));
                                   //0代表没有丢包;否则是丢包。如果他或上__flags,代表首包丢包(flag=-1)或者上一帧丢包(第9bit=1)的话,
                                   //前者的话会弃掉该帧,后者会弃掉上一帧以及随后的下一帧(并未丢包)后,才会继续处理接下来的帧数据。
                                   //建议不用或上__flags,这样即使上一帧丢包也不会影响到没有丢包的下一帧的正常处理,lost=1应该是不包含首包丢包的情况。
                                   //因为首包刚好helper->seq=0时,rtp.seq=1.即0+1=1.这个可以在这里加个if(__flags==-1){不处理包;清除上一帧内容;}
                                   //来解决首包丢包的情况即可。

        // 1.2 清除上一帧丢包的标志位,因为本帧是成功发送的。
        // 0x0100(16进制)=00000001 00000000(2进制),取反后:11111110 11111111
        // 与运算后:xxx & 11111110 11111111;实际上就是清除第9bit的标志位。或者说__flags就是使用int的一个bit。
        // 一般该语句运算后都是0。
        helper->__flags &= ~RTP_PAYLOAD_FLAG_PACKET_LOST;
    }

    // (这里是只有在rtp_payload_check时才会进来.)
    // 2 如果检测到丢包,在下一帧设置丢包标志(set packet lost flag on next frame),
    // 换句话说,就是在下一帧中记录了上一帧是有丢包的,所以上一帧是被舍弃的,可以被用于调整时间戳,__flags具有这个作用,
    // 此外还有就是记录了程序第一个rtp包是否丢包的作用。
    // 注:这里只有检测到丢包并且helper代表上一帧的情况(时间戳不同了)即rtp_payload_check时才会进来,因为遇到marker=1时是完整收到一帧,helper->lost不会=1
    if(helper->lost)
        helper->__flags |= RTP_PAYLOAD_FLAG_PACKET_LOST;

    // 3 清丢包标志和上一帧缓存(只需清size即可,不需请内存,留着继续使用),用于准备下一帧的缓存。
    helper->lost = 0;
    helper->size = 0;

    return r;
}

二 C++的思路

1 C++可以有更简单方便的思路实现,因为C++有map这种容器给我们调用。

  • 1)重复包:通过本次传进来的包的seq与本帧已经保存的全部seq进行比较(所以C++的map.find合适),若存在说明重复,丢弃即可。

  • 2)乱序:由于map可以根据first自动排序,所以可以通过遇到marker收完一帧的时候,用for进行遍历是否是按序即可,实际上这一步和下面的3)是一模一样的,所以这一步在C++不需要考虑。

  • 3)丢包:同样通过rtp包的marker标志(或者rtp包时间戳,这个不熟),当为1说明该帧结束,然后for循环进行判断,seq末是本末尾包的seq,起始一般是1.然后依次与之前保存的seq比较(例如C++的map的first),seq个个存在说明没丢包。

  • 注意:重复包、乱序与丢包的判断都是对一帧数据进行判断,判断完毕就清除本帧的内存,进行下一帧的判断。

代码实例参考这里即可:rtp协议丢包以及包重复判断

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值