AES67 TX: send each packet right after its last sample is due
The periodic TX timer had an arbitrary phase against packet boundaries, so packets waited up to one packet time (different after every restart). - One-shot esp_timer aimed at the next packet's due time from PTP, with the clock re-read after sending. - 1 kHz tone from a per-rate lookup table (one period = rate/1000 samples) instead of sinf() per sample. Measured (RTP time - arrival, 4 ms vs 1 ms packets, expected -3.07 ms incl. wire time): -3.3 ms extra before, now -3.14 ms. Tone within 1.0 LSB of the ideal PTP-phased sine, no gaps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
#define MAX_LAG_NS 20000000 // more than 20 ms behind: resync instead of bursting
|
||||
#define TONE_HZ 1000
|
||||
#define TONE_DBFS -18.0
|
||||
#define WAKE_MARGIN_US 20 // wake just after a packet's last sample is due
|
||||
|
||||
static const char *TAG = "aes67_tx";
|
||||
|
||||
@@ -166,15 +167,23 @@ static int64_t samples_to_ns(int64_t s, int rate)
|
||||
}
|
||||
|
||||
// 1 kHz tone, phase from the PTP sample index, so every sender's tone lines up.
|
||||
// One period is rate / 1000 samples (48 at 48 kHz, 96 at 96 kHz): precomputed table.
|
||||
static void tone(int32_t *buf, int frames, int channels, int rate, int64_t s0)
|
||||
{
|
||||
static float amp;
|
||||
if (!amp) {
|
||||
amp = (float)(pow(10.0, TONE_DBFS / 20.0) * 2147483647.0);
|
||||
}
|
||||
static int32_t table[96000 / TONE_HZ];
|
||||
static int table_rate;
|
||||
int period = rate / TONE_HZ;
|
||||
if (table_rate != rate) {
|
||||
double amp = pow(10.0, TONE_DBFS / 20.0) * 2147483647.0;
|
||||
for (int i = 0; i < period; i++) {
|
||||
table[i] = (int32_t)(amp * sin(2.0 * M_PI * i / period));
|
||||
}
|
||||
table_rate = rate;
|
||||
}
|
||||
int ph = (int)(s0 % period);
|
||||
for (int i = 0; i < frames; i++) {
|
||||
int32_t v = (int32_t)(amp * sinf(2.0f * (float)M_PI * (float)((s0 + i) % period) / (float)period));
|
||||
int32_t v = table[ph];
|
||||
ph = ph + 1 == period ? 0 : ph + 1;
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
buf[i * channels + ch] = v;
|
||||
}
|
||||
@@ -216,8 +225,6 @@ static void tx_task(void *arg)
|
||||
next = 0;
|
||||
if (c.enabled) {
|
||||
fd = open_socket(&c);
|
||||
// wake once per packet time; late wakes are caught up below
|
||||
esp_timer_start_periodic(timer, (uint64_t)c.frames * 1000000ULL / c.rate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +284,13 @@ static void tx_task(void *arg)
|
||||
seq++;
|
||||
next += c.frames;
|
||||
}
|
||||
// Wake when the next packet's last sample is due (PTP time), not on a free-running
|
||||
// period, so each packet leaves right after it is complete. Re-read the clock:
|
||||
// building and sending took time.
|
||||
aes67_ptp_now_ns(&now);
|
||||
int64_t wait_us = (samples_to_ns(next + c.frames, c.rate) - now) / 1000 + WAKE_MARGIN_US;
|
||||
esp_timer_stop(timer);
|
||||
esp_timer_start_once(timer, wait_us < 50 ? 50 : wait_us > 10000 ? 10000 : wait_us);
|
||||
}
|
||||
if (new_state != state) {
|
||||
state = new_state;
|
||||
|
||||
Reference in New Issue
Block a user