【问题标题】:Are linux interval timers prone to long-term average error?linux间隔定时器容易出现长期平均错误吗?
【发布时间】:2019-09-01 15:20:37
【问题描述】:

我下面的代码旨在通过每毫秒传输一次包含音频样本的 RTP 数据包来生成符合 AES67 的音频流。这是通过引用 CLOCK_REALTIME 的间隔计时器来实现的。为了满足AES67的要求,CLOCK_REALTIME通过phc2sys同步到一个PTP硬件时钟,PTP硬件时钟本身由ptp4l管理。该代码在 Beaglebone Black SBC (ARM Cortex-A8) 上运行,该 SBC 还充当启用了硬件时间戳的 PTP 大师。我已将 rt_preempt 补丁应用于内核,并且正在运行 ptp4l、phc2sys 和我的代码,实时优先级分别为 99、98 和 97。 (调度模式为 RR)。

我一直在使用硬件 AES67 接收器测试代码,它报告 PTP 同步偏移在 0.1μS 以内。系统确实可以工作,但是接收器中的接收器缓冲区会慢慢清空,并且在大约 26 分钟后运行不足。我猜是:

a) 时钟同步机制之一存在频率错误或

b) timerfd_create 创建的间隔定时器的持续时间存在长期平均误差

我还没有找到 a) 的任何证据,所以我的问题是,timerfd(或我对它的使用)是否有任何已知的限制或不足之处,可能会导致这个问题?

(定时器代码在代码示例的底部三分之一处。)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <time.h>
#include <math.h>
#include <sys/timerfd.h>
#include "random32.h"
#include "timestamp.h"

const int32_t int24_min = -8388608;
const int32_t int24_max = 8388607;

struct in_addr localInterface;
struct sockaddr_in groupSock;
struct timespec packetTime = {0, 1000000L};
struct itimerspec packetTimer;
uint16_t sequenceNumber = 0;
uint32_t timestamp = 0;
uint32_t ssrc = 0;
uint64_t expiration;
ssize_t s;
int type = 0;
int sd;
int td;
int sRate = 48000;
void tx(void);

//set the first two bytes of the RTP header
char txPacket[300] = {0b10000000, 0b01100100};
int packetLen = sizeof(txPacket);

//declare a pointer to the sequenceNumber in txPacket
char *sn = &txPacket[2];

//declare a pointer to the timestamp in txPacket
char *ts = &txPacket[4];

//declare a pointer to the synchronisation source identifier
char *ss = &txPacket[8];

//declare a pointer to the first audio sample in txPacket
char *as = &txPacket[12];

//declare a union of types int32_t and char[4] so that
//sample data can be accessed one byte at a time
union sample
{
  int32_t intSample;
  char bytes[4];
};

int main (int argc, char *argv[ ])
{
  //generate a unique ssrc and put it in the txPacket
  ssrc = htonl(random32(type));
  memcpy (ss, &ssrc, sizeof(ssrc));

  //generate 1kHz tone for the audio samples
  double angle = 0.0;
  double increment = 7.5 * (M_PI / 180);
  double amplitude;
  int i;

  union sample currentSample;
  currentSample.intSample = 0;

  for(i = 1; i < 49; i++)
  {
    //calculate amplitude based on sine of angular position
    //scale it to the maximum value of a 24-bit number
    amplitude = (sin(angle)) * int24_max;
    amplitude = round(amplitude);
    currentSample.intSample = (int32_t)amplitude;

    //copy the sample into the txpacket (twice for stereo),
    //reordiering bytes as big-endian
    memcpy (as, &currentSample.bytes[2], 1);
    as++;
    memcpy (as, &currentSample.bytes[1], 1);
    as++;
    memcpy (as, &currentSample.bytes[0], 1);
    as++;
    memcpy (as, &currentSample.bytes[2], 1);
    as++;
    memcpy (as, &currentSample.bytes[1], 1);
    as++;
    memcpy (as, &currentSample.bytes[0], 1);
    as++;

    angle = angle + increment;
  }

  //create the socket
  sd = socket(AF_INET, SOCK_DGRAM, 0);

  //initialize the groupSock structure
  memset((char *)&groupSock, 0, sizeof(groupSock));
  groupSock.sin_family = AF_INET;
  groupSock.sin_addr.s_addr = inet_addr("225.1.1.1");
  groupSock.sin_port = htons(5004);

  localInterface.s_addr = inet_addr("192.168.0.4");
  setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, (char *)&localInterface, sizeof(localInterface));

  //set the initial expiration and interval of the timer
  packetTimer.it_value = packetTime;
  packetTimer.it_interval = packetTime;

  //create and arm the timer
  td = timerfd_create(CLOCK_REALTIME, 0);
  timerfd_settime(td, TFD_TIMER_ABSTIME, &packetTimer, NULL);

  s = read(td, &expiration, sizeof(uint64_t));  //wait for a timer expiration
  timestamp = getTimestamp();                   //get initial RTP timestamp value
  tx();                                         //send first packet

  for( ; ; )
  {
    s = read(td, &expiration, sizeof(uint64_t));  //wait for next timer expiration
    tx();                                         //send next packet
  }
  return 0;
}

void tx(void)
{
  //convert sequenceNumber to network byte order and copy it to the tx packet
  uint16_t beSequenceNumber = htons(sequenceNumber);
  memcpy (sn, &beSequenceNumber, sizeof(beSequenceNumber));

  //convert timestamp to network byte order and copy it to the tx packet
  uint32_t beTimestamp = htonl(timestamp);
  memcpy (ts, &beTimestamp, sizeof(beTimestamp));

  //send packet
  sendto(sd, txPacket, packetLen, 0, (struct sockaddr*)&groupSock, sizeof(groupSock));

  //increment sequence number
  sequenceNumber++;

  //increment timestamp
  timestamp = timestamp + 48;
}

【问题讨论】:

  • 我无法回答您的问题...但我真的很高兴有人让 AES67 在这些板上运行良好,所以 +1!这么多可能的用例。

标签: time timer real-time audio-streaming rtp


【解决方案1】:

调试实时问题总是有点麻烦。有帮助的第一件事是能够通过从外部监视程序来找出程序实际在做什么。 您是否有:示波器、逻辑分析仪或任何类似的东西?

如果是这样,请更改您的程序以在读取计时器之前和读取计时器之后切换 gpio。将分析仪连接到这条 gpio 线上,并仔细测量所得波形的占空比。

如果没有,请使用真正的串行端口或某种信号识别器拼凑第二台计算机。将 beagle 上的波特率设置为相当高的值:1 MB 就足够了。在读取时间前后插入写入串口的代码。在您附加到的第二台计算机上,禁用中断,并在每次读取字符时记录时间戳。绘制这个时间序列,并仔细测量波形。

这些步骤将告诉您在及时生成数据方面做得如何。

我怀疑您的计时器没有按照您认为的方式运行。我怀疑它总是立即从读取中返回,并且缓冲和协议使它看起来工作了 26 分钟(1.5M 数据包),直到轮子掉下来。但这只是喃喃自语的假设。测量告诉我们真实的故事。

另一方面,如果您的计时器运行正常,您应该会看到它在 26 分钟内缓慢漂移,这表明 pptp 不太正确,或者您应该使用单调计时器。

【讨论】:

  • 谢谢,我可以在工作中访问'范围,所以我会尝试这个测试。我认为系统工作了约 26 分钟,因为接收器可以将流捕获到文件中,这会显示出预期的完整正弦波。接收器还提供缓冲区填充与时间的关系图。这表明填充水平随时间从初始健康状态降低到零。有趣的是,这不是一条趋于零的直线,而是一个阶梯图,其中缓冲区稳定,然后突然下降 48 个样本,然后重复..(48 是一个 RTP 数据包中的音频样本数。)
  • 我添加了一些日志来记录每次读取计时器之间发生的计时器到期次数。当然,这应该始终为 1,但它会发现它有时不止于此。将计时器间隔增加到 2ms 可消除此错误。 (不幸的是,AES67 中不支持 2ms 的“数据包时间”。)我将尝试在更强大的*系统上运行我的代码,看看这是否有积极影响。 *更强大的是更高的时钟速度和多核,因此我可以将 CPU 内核专用于我的数据包传输例程。
  • 我的大部分职业生涯都是在实时操作系统上度过的。早期,我经常接触到“实时足够快”;然而,尽管看到平均指令速率从大约 1M/s 到 1000M/s; “足够快从未达到实时”。实时是针对最坏情况进行优化并争取有限响应的实施选择。即使是非常快的 CPU 最终也会出现病态的情况。如果您正在为网站提供服务,那没关系;冷却棒则更少。
  • 获取Linux,应用实时补丁。测量,测量,测量。关于为什么 Q 不是你想要的,请选择 Steve Rostedt(告诉他是我发给你的)。如果你真的很沮丧,计算一下商业产品的成本——QNX、VxWorks、Nucleus……
  • 再次感谢 mevets。我现在在多核机器上运行实时内核补丁。我的 PTP 守护程序和我的音频 tx 代码在不同的内核上运行,并且已经实现了超过 16 小时的稳定音频流(进入硬件接收器)而没有中断。我猜 Beaglebone 平台对于这项工作来说有点资源限制,如果要可用,可能需要进一步优化。好消息是 timerfd 似乎确实足够准确,只需要确保在到期之间有足够的时钟周期来完成其他工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-01
相关资源
最近更新 更多