Files
github-actions[bot] f8e13417f2 sunnypilot v2026.003.000 release
date: 2026-08-15T14:20:10
master commit: d48cfa730bf21593b684efeaf3fd8940695e1dea
2026-08-15 14:20:16 +08:00

237 lines
6.7 KiB
Python
Executable File

#!/usr/bin/env python3
import contextlib
import pytest
from libdatachannel import Description
from teleoprtc.builder import WebRTCOfferBuilder, WebRTCAnswerBuilder
from teleoprtc.info import parse_info_from_offer
from teleoprtc.stream import H264_FORMAT_PARAMETERS
from teleoprtc.tracks import TiciVideoStreamTrack
class OfferCapture:
def __init__(self):
self.offer = None
async def __call__(self, offer):
self.offer = offer
raise Exception("Offer captured")
class DummyH264VideoStreamTrack(TiciVideoStreamTrack):
kind = "video"
async def recv(self):
raise NotImplementedError()
@pytest.mark.asyncio
class TestOfferStream:
async def test_offer_stream_sdp_recvonly_audio(self):
capture = OfferCapture()
builder = WebRTCOfferBuilder(capture)
builder.offer_to_receive_audio_stream()
stream = builder.stream()
try:
_ = await stream.start()
except Exception:
pass
finally:
await stream.stop()
info = parse_info_from_offer(capture.offer.sdp)
assert info.expected_audio_track
assert not info.incoming_audio_track
async def test_offer_stream_sdp_channel(self):
capture = OfferCapture()
builder = WebRTCOfferBuilder(capture)
builder.add_messaging()
stream = builder.stream()
try:
_ = await stream.start()
except Exception:
pass
finally:
await stream.stop()
info = parse_info_from_offer(capture.offer.sdp)
assert info.incoming_datachannel
async def test_answer_side_messaging_channel_wrapper_retained_after_stop(self):
stream = WebRTCOfferBuilder(OfferCapture()).stream()
stream._retain_messaging_channel_on_close = True
channel = stream.peer_connection.create_data_channel("data")
stream.messaging_channel = channel
stream.messaging_channel_ready_event.set()
try:
await stream.stop()
assert stream.messaging_channel is None
assert stream._retained_messaging_channels[-1] is channel
finally:
stream._retained_messaging_channels.pop()
@pytest.mark.asyncio
class TestAnswerStream:
async def test_codec_preference(self):
offer_sdp = """v=0
o=- 3910274679 3910274679 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE 0
a=msid-semantic:WMS *
m=video 1337 UDP/TLS/RTP/SAVPF 97 98 99 100 101 102
c=IN IP4 0.0.0.0
a=recvonly
a=mid:0
a=msid:34803878-98f8-4245-b45c-f773e5f926df 881dbc20-356a-499c-b4e8-695303bb901d
a=rtcp:9 IN IP4 0.0.0.0
a=rtcp-mux
a=ssrc-group:FID 1303546896 3784011659
a=ssrc:1303546896 cname:a59185ac-c115-48d3-b39b-db7d615a6966
a=ssrc:3784011659 cname:a59185ac-c115-48d3-b39b-db7d615a6966
a=rtpmap:97 VP8/90000
a=rtcp-fb:97 nack
a=rtcp-fb:97 nack pli
a=rtcp-fb:97 goog-remb
a=rtpmap:99 H264/90000
a=rtcp-fb:99 nack
a=rtcp-fb:99 nack pli
a=rtcp-fb:99 goog-remb
a=fmtp:99 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=ice-ufrag:1234
a=ice-pwd:1234
a=fingerprint:sha-256 15:F3:F0:23:67:44:EE:2C:AA:8C:D9:50:95:26:42:7C:67:EA:1F:D2:92:C5:97:01:7B:2E:57:C9:A3:13:00:4A
a=setup:actpass"""
builder = WebRTCAnswerBuilder(offer_sdp)
builder.add_video_stream("road", DummyH264VideoStreamTrack("road", 0.05))
stream = builder.stream()
try:
answer = await stream.start()
sdp_desc = Description(answer.sdp, Description.Type.Answer)
video_desc = next(sdp_desc.media(i) for i in range(sdp_desc.media_count()) if sdp_desc.media(i).type() == "video")
codecs = []
for pt in video_desc.payload_types():
with contextlib.suppress(ValueError):
codecs.append(video_desc.rtp_map(pt).format)
assert codecs[0] == "H264"
finally:
await stream.stop()
async def test_fail_if_preferred_codec_not_in_offer(self):
offer_sdp = """v=0
o=- 3910274679 3910274679 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE 0
a=msid-semantic:WMS *
m=video 1337 UDP/TLS/RTP/SAVPF 97 98 99 100 101 102
c=IN IP4 0.0.0.0
a=recvonly
a=mid:0
a=msid:34803878-98f8-4245-b45c-f773e5f926df 881dbc20-356a-499c-b4e8-695303bb901d
a=rtcp:9 IN IP4 0.0.0.0
a=rtcp-mux
a=ssrc-group:FID 1303546896 3784011659
a=ssrc:1303546896 cname:a59185ac-c115-48d3-b39b-db7d615a6966
a=ssrc:3784011659 cname:a59185ac-c115-48d3-b39b-db7d615a6966
a=rtpmap:97 VP8/90000
a=rtcp-fb:97 nack
a=rtcp-fb:97 nack pli
a=rtcp-fb:97 goog-remb
a=ice-ufrag:1234
a=ice-pwd:1234
a=fingerprint:sha-256 15:F3:F0:23:67:44:EE:2C:AA:8C:D9:50:95:26:42:7C:67:EA:1F:D2:92:C5:97:01:7B:2E:57:C9:A3:13:00:4A
a=setup:actpass"""
builder = WebRTCAnswerBuilder(offer_sdp)
builder.add_video_stream("road", DummyH264VideoStreamTrack("road", 0.05))
stream = builder.stream()
try:
with pytest.raises(ValueError):
_ = await stream.start()
finally:
await stream.stop()
async def test_multi_track(self):
offer_sdp = """v=0
o=- 1 1 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE 0 1
m=video 9 UDP/TLS/RTP/SAVPF 99
c=IN IP4 0.0.0.0
a=recvonly
a=mid:0
a=rtpmap:99 H264/90000
a=ice-ufrag:1234
a=ice-pwd:1234
a=fingerprint:sha-256 15:F3:F0:23:67:44:EE:2C:AA:8C:D9:50:95:26:42:7C:67:EA:1F:D2:92:C5:97:01:7B:2E:57:C9:A3:13:00:4A
a=setup:actpass
m=video 9 UDP/TLS/RTP/SAVPF 99
c=IN IP4 0.0.0.0
a=recvonly
a=mid:1
a=rtpmap:99 H264/90000
a=ice-ufrag:1234
a=ice-pwd:1234
a=fingerprint:sha-256 15:F3:F0:23:67:44:EE:2C:AA:8C:D9:50:95:26:42:7C:67:EA:1F:D2:92:C5:97:01:7B:2E:57:C9:A3:13:00:4A
a=setup:actpass"""
builder = WebRTCAnswerBuilder(offer_sdp)
builder.add_video_stream("road", DummyH264VideoStreamTrack("road", 0.05))
builder.add_video_stream("wideRoad", DummyH264VideoStreamTrack("wideRoad", 0.05))
stream = builder.stream()
try:
answer = await stream.start()
sdp_desc = Description(answer.sdp, Description.Type.Answer)
video_ssrcs = []
for i in range(sdp_desc.media_count()):
media = sdp_desc.media(i)
if media and media.type() == "video":
video_ssrcs.extend(media.get_ssrcs())
assert len(video_ssrcs) == 2
assert len(set(video_ssrcs)) == 2
video_sections = list(answer.sdp.split("m=video")[1:])
assert all(f"a=fmtp:99 {H264_FORMAT_PARAMETERS}" in section for section in video_sections)
finally:
await stream.stop()
async def test_parse_incoming_streams_datachannel_counting(self):
offer_sdp = """v=0
o=- 3910274679 3910274679 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE 0 1
m=video 9 UDP/TLS/RTP/SAVPF 97
c=IN IP4 0.0.0.0
a=recvonly
a=mid:0
a=ice-ufrag:1234
a=ice-pwd:1234
a=fingerprint:sha-256 15:F3:F0:23:67:44:EE:2C:AA:8C:D9:50:95:26:42:7C:67:EA:1F:D2:92:C5:97:01:7B:2E:57:C9:A3:13:00:4A
a=setup:actpass
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=sendrecv
a=mid:1
a=sctp-port:5000"""
builder = WebRTCAnswerBuilder(offer_sdp)
stream = builder.stream()
stream._parse_incoming_streams(builder.offer_sdp)
assert stream.expected_number_of_incoming_media == 1
await stream.stop()