fix RTP packet pacing
management-images / build-and-publish (push) Successful in 34s

This commit is contained in:
2026-09-17 18:46:51 +08:00
parent 576cc95dab
commit 758c0a29e6
2 changed files with 41 additions and 1 deletions
+11 -1
View File
@@ -340,6 +340,7 @@ class RTPMedia:
self.sequence = 0 self.sequence = 0
self.timestamp = 0 self.timestamp = 0
self.ssrc = uuid.uuid4().int & 0xFFFFFFFF self.ssrc = uuid.uuid4().int & 0xFFFFFFFF
self._next_send_at: float | None = None
self._send_lock = threading.Lock() self._send_lock = threading.Lock()
@property @property
@@ -399,11 +400,18 @@ class RTPMedia:
) )
payload = pcm16_to_alaw(raw, input_rate_hz, 8000) payload = pcm16_to_alaw(raw, input_rate_hz, 8000)
sent = 0 sent = 0
packet_interval_s = 0.02
with self._send_lock: with self._send_lock:
now = time.monotonic()
if self._next_send_at is None or self._next_send_at < now - packet_interval_s:
self._next_send_at = now
for offset in range(0, len(payload), 160): for offset in range(0, len(payload), 160):
frame = payload[offset : offset + 160] frame = payload[offset : offset + 160]
if not frame: if not frame:
continue continue
delay = self._next_send_at - time.monotonic()
if delay > 0:
time.sleep(delay)
header = struct.pack( header = struct.pack(
"!BBHII", "!BBHII",
0x80, 0x80,
@@ -421,7 +429,9 @@ class RTPMedia:
self.sequence += 1 self.sequence += 1
self.timestamp += 160 self.timestamp += 160
sent += len(frame) sent += len(frame)
time.sleep(0.02) self._next_send_at = max(
self._next_send_at + packet_interval_s, time.monotonic()
)
return sent return sent
@staticmethod @staticmethod
+30
View File
@@ -10,6 +10,7 @@ import wave
from pathlib import Path from pathlib import Path
from types import SimpleNamespace from types import SimpleNamespace
from typing import Any, cast from typing import Any, cast
from unittest.mock import patch
from agent_call.real_cell import ( from agent_call.real_cell import (
CellCallConfig, CellCallConfig,
@@ -154,6 +155,35 @@ class RealCellTests(unittest.TestCase):
self.assertEqual(fake_ari.record_requests, 0) self.assertEqual(fake_ari.record_requests, 0)
self.assertNotIn("opening", order) self.assertNotIn("opening", order)
def test_rtp_send_keeps_packet_clock_across_frames(self) -> None:
media = RTPMedia("127.0.0.1", 0)
receiver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
receiver.bind(("127.0.0.1", 0))
media.peer = ("127.0.0.1", receiver.getsockname()[1])
clock = [0.0]
sleeps: list[float] = []
def monotonic() -> float:
return clock[0]
def sleep(seconds: float) -> None:
sleeps.append(seconds)
clock[0] += seconds
try:
with (
patch("agent_call.real_cell.time.monotonic", monotonic),
patch("agent_call.real_cell.time.sleep", sleep),
):
media.send_pcm16(bytes([0, 0]) * 160, 8000)
media.send_pcm16(bytes([0, 0]) * 160, 8000)
self.assertEqual(sleeps, [0.02])
self.assertEqual(len(receiver.recvfrom(512)[0]), 172)
self.assertEqual(len(receiver.recvfrom(512)[0]), 172)
finally:
media.close()
receiver.close()
def test_pcma_decode_and_rtp_payload(self) -> None: def test_pcma_decode_and_rtp_payload(self) -> None:
media = RTPMedia("127.0.0.1", 0) media = RTPMedia("127.0.0.1", 0)
receiver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) receiver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)