rtp rtcp av sync

本文详细探讨了音视频流在网络传输中的同步问题,通过使用开源库jrtplib3.7.1实现rtp协议标准,介绍了如何在数据中加入时间戳,接收端通过比较时间戳进行同步处理。针对不同网络条件下的同步挑战,提出了具体解决方案,包括单条数据流与多条数据流的同步方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >




rtp同步方法的思考

由于音视频流是以两条独立的数据流在网络上传输的,如果网络质量相当差,那么在接收端收到的音视频数据流就有可能不是同步的了,为了克服这种不同步的现象,需要添加同步机制。的同步机制是使用开源库jrtplib3.7.1来实现的,严格遵守rtp协议标准。

解决的方案如下:

       当有数据需要发送时,往数据中加入时间戳,在接收端,读取时间戳,进行比较,如果相同或相差很近,就提交播放,如果其中一个时间戳更大,就等待。

如果网络质量很差,那么存在两种不同步的情况:

1.                  对于单条数据流来说,如果网络质量很差,可能出现数据流的接收不流畅,如果没有做流畅处理,那么就可能出现抖动现象,这需要使用rtp中的时间戳解决。

2.                  对于多条数据流来说,如果网络质量很差,可能出现本应该同时播放的数据帧没有在同一时间到达,需要做同步处理。

解决第1个问题的方法是向每个发送的数据包加上时间戳,在rtp库中,时间戳表示在打包数据段中第一个采样所对应的时间,时间戳的启始值是随机的,后续的时间戳是在前一个时间戳上的增量,在SendPacket中的时间戳参数表示的是时间戳增量,所以数据流的同步需要计算出时间戳增量。

对于音频数据,由于音频数据的采样率是8000HZ,所以每采样一次需要时间是1/8000s,由于是每20ms封包一次,所以时间戳的增量是(20*10**-3)*8000=160。

对于视频数据,由于视频数据的采样率是90000Hz,所以每采样一次需要时间是1/90000s,如果帧率是25帧/s,所以时间戳增量是90000/25=3600。

在发送端,每发送一个数据包,都打上该数据包对于的时间戳值,只需要向SendPacket的最后个参数传递时间戳增量,rtp库会自动算出时间戳,并加到发送的rtp数据包首部里边。

在接收端,当收到一个数据包时,获取该rtp数据包的时间戳值,计算出与前一个数据包的时间戳值的差值,乘以该媒体流的时间戳单位,就得出了当前数据包与前一个数据包之间的间隔的打包时间T。所以只要保证在与前一个数据包被提交过后T时间后再提交当前接收到的数据包,那么在rtp层就解决了上边提出的第一个问题。

解决第2个问题的方法是使用rtcp发送者报告数据包中的时间信息,发送者报告被发送的间隔时间是不固定的,它的大小与参与到会话中的同步源数量成正比。每个发送者报告中都有个ntp时间和rtp时间,该ntp时间表示在发送时间戳为该rtp时间的rtp包时的系统绝对时间,就是在发送这个rtp包时的系统时间,所以这两个时间值有对应关系,由于对于一个同步源,时间戳单位是固定的,所以可以由后续的某个数据包的rtp时间戳timestamp1计算出这个数据包所对应的绝对时间,这个绝对时间就是在发送这个数据包时,发送端的系统时间

这个思路就是:

已知:ntp0,rtp0,rtp1,时间戳单位u,且ntp0与rtp0是表示同一时间值,只是测量标尺不同。

计算:ntp1,该ntp1与rtp1表示同一时间值

计算的公式:

ntp1 = ntp0 + (rtp1- rtp0)*u

使用这种方法就可以计算出每个收到的rtp数据包在发送端的系统时间,这个系统时间在发送端是单调递增的,所以可以通过这个值来同步多条数据流。

发送端(需要修改):

if(mediatype == 0)
{
   if(first_flag)
   {
    //usleep(125*266*30);
    presampletime = RTPTime::CurrentTime();

    status = rtpsession->SendPacket((void *)buf, len, payloadtype, false, 0);
    first_flag = false;
   }else
   {

    tmpcurtime = curtime = RTPTime::CurrentTime();

    curtime-=presampletime;
    interval = (uint32_t)(curtime.GetDouble() * 8000.0);
  
    status = rtpsession->SendPacket((void *)buf, len, payloadtype, false, 160/*interval*/);

    presampletime = tmpcurtime;
   }
}else
{

   GwRTPRecv::g_start_send = 1;
   if(GwRTPRecv::g_skip_frame==1)
   {
    char buf123[]="#1234#";
    status = rtpsession->SendPacket((void *)buf123, strlen(buf123), payloadtype,true,0);
    GwRTPRecv::g_skip_frame = 2;
   }
   if(GwMpeg4Codec::g_key_frame)
   {
    if(buf[10] != 'I')
    {
     return false;
   
    }else if(GwMpeg4Codec::g_key_frame==2)
    {
     GwMpeg4Codec::g_key_frame = 0;
     first_flag = true;
    }
   }

   if(first_flag)
   {
    presampletime = RTPTime::CurrentTime();
    status = rtpsession->SendPacket((void *)buf, len, payloadtype, false, 0);
    first_flag = false;
   }else
   {
    tmpcurtime = curtime = RTPTime::CurrentTime();
    curtime-=presampletime;
    interval = (uint32_t)(curtime.GetDouble() * 90000.0);
  
    status = rtpsession->SendPacket((void *)buf, len, payloadtype, false, interval);

    presampletime = tmpcurtime;
   }
}

接收端(需要修改):

bool recved = false;
RTPPacket *pack;
uint8_t *data ;
RTPTime curtime(0,0);
uint32_t timestamp;
RTPTime delay_time(0, 0);

uint32_t rtcp_timestamp;
RTPNTPTime rtcp_ntptime(0,0);

double interval_time;
double interval_time_abs;

RTPSourceData* psourcedata;

char need_sync_flag = false;

if(mediatype == 0)
{
   rtpsession->BeginDataAccess();
   if (rtpsession->GotoFirstSourceWithData())
   {
    do
    {
     while ((pack = rtpsession->GetNextPacket()) != NULL)
     {
      if(pack->GetPayloadType() != payloadtype)
      {
       rtpsession->DeletePacket(pack);
       continue;
      }
      recvlen = pack->GetPayloadLength();
      if(recvlen > pack_size|| recvlen <= 0 )
      {
       rtpsession->DeletePacket(pack);
       continue;
      }

      timestamp = pack->GetTimestamp();
    
      data = pack->GetPayloadData();
      *len = recvlen;
      memcpy(buf, data, recvlen);
      recved = true;
    
      psourcedata = rtpsession->GetCurrentSourceInfo();
      if(psourcedata->SR_HasInfo())
      {
       rtcp_timestamp = psourcedata->SR_GetRTPTimestamp();
       rtcp_ntptime = psourcedata->SR_GetNTPTimestamp();
       recvd_rtcp = true;
      }

      if(recvd_rtcp)
      {
       RTPTime rtcpntptime(rtcp_ntptime);
     
       uint32_t timestamp_bound = timestamp > rtcp_timestamp?(timestamp-rtcp_timestamp):(rtcp_timestamp-timestamp);
     
       if(timestamp_bound > 0xffff0000)
       {
        interval_time_abs = (0xffffffff - timestamp_bound)/8000.0;
        interval_time = timestamp < rtcp_timestamp?(interval_time_abs):(-interval_time_abs);
       }else
       {
        interval_time_abs = timestamp_bound/8000.0;
        interval_time = timestamp < rtcp_timestamp?(-interval_time_abs):(interval_time_abs);
       }
       RTPTime rtptime_interval(interval_time_abs);

       if(interval_time > 0)
       {
        rtcpntptime+=rtptime_interval;
       }else
       {
        rtcpntptime-=rtptime_interval;
       }
       audio_time_sem.lockSem();
       audio_curtime = rtcpntptime;
       audio_time_sem.unlockSem();
     
      }

      rtpsession->DeletePacket(pack);
      goto aurecv_end;

     }
    } while (rtpsession->GotoNextSourceWithData());
   }
aurecv_end:
   rtpsession->EndDataAccess();

}else
{

   rtpsession->BeginDataAccess();
   if (rtpsession->GotoFirstSourceWithData())
   {
    do
    {
     while ((pack = rtpsession->GetNextPacket()) != NULL)
     {

      if(pack->GetPayloadType() != payloadtype)
      {
       rtpsession->DeletePacket(pack);
       continue;
      }
      recvlen = pack->GetPayloadLength();
      if(recvlen > pack_size|| recvlen <= 0 )
      {
       rtpsession->DeletePacket(pack);
       continue;
      }
      timestamp = pack->GetTimestamp();
      data = pack->GetPayloadData();
      *len = recvlen;
      memcpy(buf, data, recvlen);
      recved = true;
    
      if(pack->HasMarker() && strncmp((char*)data, "#1234#", 6) == 0)
      {
       rtpsession->DeletePacket(pack);
       goto verecv_end;
      }
      if(buffer[0]=='#' && buffer[3]=='*' && buffer[2] == 'I')
      {    
       first_flag = true;
      }


      psourcedata = rtpsession->GetCurrentSourceInfo();
      if(psourcedata->SR_HasInfo())
      {
       rtcp_timestamp = psourcedata->SR_GetRTPTimestamp();
       rtcp_ntptime = psourcedata->SR_GetNTPTimestamp();
       recvd_rtcp = true;
      }
      if(recvd_rtcp)
      {
       RTPTime rtcpntptime(rtcp_ntptime);
     
       uint32_t timestamp_bound = timestamp > rtcp_timestamp?(timestamp-rtcp_timestamp):(rtcp_timestamp-timestamp);
     
       if(timestamp_bound > 0xffff0000)
       {
        interval_time_abs = (0xffffffff - timestamp_bound)/90000.0;
        interval_time = timestamp < rtcp_timestamp?(interval_time_abs):(-interval_time_abs);
       }else
       {
        interval_time_abs = timestamp_bound/90000.0;
        interval_time = timestamp < rtcp_timestamp?(-interval_time_abs):(interval_time_abs);
       }

       RTPTime rtptime_interval(interval_time_abs);

       if(interval_time > 0)
       {
        rtcpntptime+=rtptime_interval;
       }else
       {
        rtcpntptime-=rtptime_interval;
       }
     
       audio_time_sem.lockSem();
       RTPTime audio_curtime1 = audio_curtime;
       audio_time_sem.unlockSem();

       if((audio_curtime1.GetSeconds() != 0 ||audio_curtime1.GetMicroSeconds() != 0))
       {
        if(rtcpntptime < audio_curtime1)
        {
         RTPTime tmp = audio_curtime1;
         if((tmp-=rtcpntptime) > RTPTime(0, 500000))
         {
          first_flag = true;   /*make vedio stream receive faster*/
          *len = 0;
          recved = false;   /*discard packet becase it is too late*/
          goto verecv_end;
         }
        }else
        {
         if(first_sync_flag)
         {
          presynctime = RTPTime::CurrentTime();
          first_sync_flag = false;
          need_sync_flag = true;
         }else
         {
          curtime = RTPTime::CurrentTime();
          RTPTime tmp = curtime;
          tmp -= presynctime;
          if(tmp >= RTPTime(5,0))
          {
           need_sync_flag = true;
           presynctime = curtime;
          }
         }


         if(need_sync_flag)
         {
          rtcpntptime-=RTPTime(0, 1000);
          while ( audio_recving && rtcpntptime > audio_curtime1 ) /*vedio advance*/
          {
           audio_time_sem.lockSem();
           audio_curtime1 = audio_curtime;
           audio_time_sem.unlockSem();
           usleep(5);
          }
          first_flag = true;
         }
        }
      
       }
     
      }

      if(first_flag)
      {
       preplaytime = RTPTime::CurrentTime();
       pretimestamp = timestamp;
       first_flag = false;

      }else
      {
       double delay;
       if(timestamp < pretimestamp)
       {
        delay = (0xffffffff - pretimestamp + timestamp)/90000.0;
       }else
       {
        delay = (timestamp - pretimestamp)/90000.0;
       }
       RTPTime delay_time(delay);

       curtime = RTPTime::CurrentTime();
       curtime-=preplaytime;
       if(delay_time > curtime)
       {
        delay_time -= curtime;
       }
       preplaytime = RTPTime::CurrentTime();
       preplaytime+=delay_time;
       pretimestamp = timestamp;
      }
    
      rtpsession->DeletePacket(pack);
      goto verecv_end;
     }

    }while (rtpsession->GotoNextSourceWithData());
   }

verecv_end:
        rtpsession->EndDataAccess();
   if(delay_time.GetSeconds() != 0 || delay_time.GetMicroSeconds() !=0)
   {
    RTPTime::Wait(delay_time);
   }
}

return recved;




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值