Skip to content

Commit 30e1e77

Browse files
Copilotcodingjoe
andcommitted
Cleanup: remove RTP from SIP, fold RegisterSIP in, clean __init__, delete call.py
- voip/rtp.py: RTP now extends asyncio.DatagramProtocol directly (strips RTP header, calls audio_received). No wrapper class needed. RealtimeTransportProtocol kept as backward-compat alias. - voip/sip/protocol.py: removed _RTPProtocol and 'from voip.rtp' import. RegisterSIP folded in from session.py. RTP referenced only under TYPE_CHECKING. call_class passed directly to create_datagram_endpoint (RTP IS the protocol). - voip/sip/session.py: deleted (RegisterSIP lives in protocol.py now). - voip/call.py: deleted entirely (no backward-compat shim). - voip/__init__.py: minimal — only version info, no global class imports. - voip/sip/__init__.py: exports SIP, RegisterSIP, SessionInitiationProtocol. - tests/test_rtp.py: updated to use RTP class directly. - tests/test_call.py: updated logger names (session->protocol). Co-authored-by: codingjoe <[email protected]>
1 parent d0b6564 commit 30e1e77

File tree

8 files changed

+262
-302
lines changed

8 files changed

+262
-302
lines changed

tests/test_call.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ def test_response_received__200_ok__logs_info(self, caplog):
716716

717717
p = make_register_session()
718718
p.connection_made(make_mock_transport())
719-
with caplog.at_level(logging.INFO, logger="voip.sip.session"):
719+
with caplog.at_level(logging.INFO, logger="voip.sip.protocol"):
720720
p.response_received(
721721
Response(status_code=200, reason="OK", headers={"CSeq": "1 REGISTER"}),
722722
("192.0.2.2", 5060),
@@ -729,7 +729,7 @@ def test_response_received__unexpected_status__logs_warning(self, caplog):
729729

730730
p = make_register_session()
731731
p.connection_made(make_mock_transport())
732-
with caplog.at_level(logging.WARNING, logger="voip.sip.session"):
732+
with caplog.at_level(logging.WARNING, logger="voip.sip.protocol"):
733733
with pytest.raises(NotImplementedError):
734734
p.response_received(
735735
Response(

tests/test_rtp.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import struct
66

77
import pytest
8-
from voip.rtp import RealtimeTransportProtocol, RTPPacket, RTPPayloadType
8+
from voip.rtp import RTP, RTPPacket, RTPPayloadType
99

1010

1111
def make_rtp_packet(
@@ -78,16 +78,22 @@ def test_parse__marker_bit_ignored_in_payload_type(self):
7878
assert packet.payload_type == 111
7979

8080

81-
class TestRTPProtocol:
81+
class TestRTP:
8282
def test_rtp_header_size__class_attribute(self):
8383
"""rtp_header_size is a class attribute set to the standard 12-byte header."""
84-
assert RealtimeTransportProtocol.rtp_header_size == 12
84+
assert RTP.rtp_header_size == 12
85+
86+
def test_rtp__is_datagram_protocol(self):
87+
"""RTP is an asyncio.DatagramProtocol subclass."""
88+
import asyncio
89+
90+
assert issubclass(RTP, asyncio.DatagramProtocol)
8591

8692
def test_datagram_received__forwards_audio_payload(self):
8793
"""Strip the RTP header and forward the audio payload to audio_received."""
8894
received: list[bytes] = []
8995

90-
class ConcreteRTP(RealtimeTransportProtocol):
96+
class ConcreteRTP(RTP):
9197
def audio_received(self, data: bytes) -> None:
9298
received.append(data)
9399

@@ -100,7 +106,7 @@ def test_datagram_received__skips_packet_shorter_than_header(self):
100106
"""Skip packets shorter than the 12-byte RTP header."""
101107
received: list[bytes] = []
102108

103-
class ConcreteRTP(RealtimeTransportProtocol):
109+
class ConcreteRTP(RTP):
104110
def audio_received(self, data: bytes) -> None:
105111
received.append(data)
106112

@@ -111,7 +117,7 @@ def test_datagram_received__skips_header_only_packet(self):
111117
"""Skip packets that contain only the 12-byte header with no audio payload."""
112118
received: list[bytes] = []
113119

114-
class ConcreteRTP(RealtimeTransportProtocol):
120+
class ConcreteRTP(RTP):
115121
def audio_received(self, data: bytes) -> None:
116122
received.append(data)
117123

voip/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
"""Python asyncio library for VoIP calls."""
22

33
from . import _version
4-
from .rtp import RTP
5-
from .sip import SIP, RegisterSIP, SessionInitiationProtocol
6-
from .sip.messages import Request, Response
74

85
__version__ = _version.version
96
VERSION = _version.version_tuple
10-
11-
__all__ = ["RTP", "RegisterSIP", "Request", "Response", "SIP", "SessionInitiationProtocol"]

voip/call.py

Lines changed: 0 additions & 11 deletions
This file was deleted.

voip/rtp.py

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import enum
88
import logging
99

10-
__all__ = ["RTP", "RTPPacket", "RTPPayloadType", "RealtimeTransportProtocol"]
10+
__all__ = ["RTP", "RTPPacket", "RTPPayloadType"]
1111

1212
logger = logging.getLogger(__name__)
1313

@@ -53,40 +53,34 @@ def parse(cls, data: bytes) -> RTPPacket:
5353
)
5454

5555

56-
class RTP:
56+
class RTP(asyncio.DatagramProtocol):
5757
"""Base class for RTP audio call handlers (RFC 3550).
5858
5959
Subclass this and override :meth:`audio_received` to process incoming audio::
6060
6161
class MyCall(RTP):
6262
def audio_received(self, data: bytes) -> None:
6363
... # process Opus audio payload
64+
65+
Instances are used directly as asyncio datagram protocols, so they handle
66+
their own RTP header stripping before calling :meth:`audio_received`.
6467
"""
6568

69+
#: Fixed RTP header size in bytes (RFC 3550 §5.1).
70+
rtp_header_size: int = 12
71+
6672
def __init__(self, caller: str = "") -> None:
6773
#: The SIP address of the caller (from the From header of the INVITE).
6874
self.caller = caller
6975

70-
def audio_received(self, data: bytes) -> None:
71-
"""Handle a decoded RTP audio payload. Override in subclasses."""
72-
73-
74-
class RealtimeTransportProtocol(asyncio.DatagramProtocol):
75-
"""
76-
Real-time Transport Protocol (RTP) asyncio protocol for receiving audio streams.
77-
78-
This is an internal asyncio protocol that strips the RTP header and forwards
79-
the audio payload. End-users should subclass :class:`RTP` instead.
80-
81-
See also: https://datatracker.ietf.org/doc/html/rfc3550#section-5
82-
"""
83-
84-
rtp_header_size = 12
85-
8676
def datagram_received(self, data: bytes, addr: tuple[str, int]) -> None:
8777
"""Strip the fixed RTP header and forward the audio payload."""
8878
if len(data) > self.rtp_header_size:
89-
self.audio_received(data[self.rtp_header_size :])
79+
self.audio_received(data[self.rtp_header_size:])
9080

9181
def audio_received(self, data: bytes) -> None:
9282
"""Handle a decoded RTP audio payload. Override in subclasses."""
83+
84+
85+
#: Backward-compatible alias.
86+
RealtimeTransportProtocol = RTP

voip/sip/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Session Initiation Protocol (SIP) implementation of RFC 3261."""
22

3-
from .protocol import SIP, SessionInitiationProtocol
4-
from .session import RegisterSIP
3+
from .protocol import SIP, RegisterSIP, SessionInitiationProtocol
54

65
__all__ = ["RegisterSIP", "SIP", "SessionInitiationProtocol"]

0 commit comments

Comments
 (0)