forked from IQ.Lvbs/IQ.Pilot
IQ.Pilot Prebuilt Release @ ab07000
This commit is contained in:
146
teleoprtc_repo/tests/test_info.py
Executable file
146
teleoprtc_repo/tests/test_info.py
Executable file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from teleoprtc.info import parse_info_from_offer
|
||||
|
||||
|
||||
def lf2crlf(x):
|
||||
return x.replace("\n", "\r\n")
|
||||
|
||||
|
||||
class TestStream:
|
||||
def test_double_video_tracks(self):
|
||||
sdp = """v=0
|
||||
o=- 3910210993 3910210993 IN IP4 0.0.0.0
|
||||
s=-
|
||||
t=0 0
|
||||
a=group:BUNDLE 0 1
|
||||
a=msid-semantic:WMS *
|
||||
m=video 9 UDP/TLS/RTP/SAVPF 97 98 99 100 101 102
|
||||
c=IN IP4 0.0.0.0
|
||||
a=recvonly
|
||||
a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
||||
a=mid:0
|
||||
a=msid:e123f852-010c-4b7b-8761-71b72fbfd013 2b75cb0e-6b34-48d6-8bf9-21b809f2e08e
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=rtcp-mux
|
||||
a=ssrc-group:FID 1048118556 4149054509
|
||||
a=ssrc:1048118556 cname:61992fce-bab5-42a0-ab8c-7112adfb1857
|
||||
a=ssrc:4149054509 cname:61992fce-bab5-42a0-ab8c-7112adfb1857
|
||||
a=rtpmap:97 VP8/90000
|
||||
a=rtcp-fb:97 nack
|
||||
a=rtcp-fb:97 nack pli
|
||||
a=rtcp-fb:97 goog-remb
|
||||
a=rtpmap:98 rtx/90000
|
||||
a=fmtp:98 apt=97
|
||||
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=rtpmap:100 rtx/90000
|
||||
a=fmtp:100 apt=99
|
||||
a=rtpmap:101 H264/90000
|
||||
a=rtcp-fb:101 nack
|
||||
a=rtcp-fb:101 nack pli
|
||||
a=rtcp-fb:101 goog-remb
|
||||
a=fmtp:101 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
|
||||
a=rtpmap:102 rtx/90000
|
||||
a=fmtp:102 apt=101
|
||||
a=ice-ufrag:jxQW
|
||||
a=ice-pwd:KpJ0tfaY2RxnIYpTHqPSSv
|
||||
a=fingerprint:sha-256 70:3A:2D:37:3C:52:96:0E:10:F6:4D:7A:EB:18:38:1B:FD:CA:A5:90:D7:6C:DA:A9:39:76:C9:2F:FB:FF:56:0C
|
||||
a=setup:actpass
|
||||
m=video 9 UDP/TLS/RTP/SAVPF 97 98 99 100 101 102
|
||||
c=IN IP4 0.0.0.0
|
||||
a=recvonly
|
||||
a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
||||
a=mid:1
|
||||
a=msid:e123f852-010c-4b7b-8761-71b72fbfd013 311db759-8d51-479c-a5b4-5c8d055c43ec
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=rtcp-mux
|
||||
a=ssrc-group:FID 4096183284 2713379498
|
||||
a=ssrc:4096183284 cname:61992fce-bab5-42a0-ab8c-7112adfb1857
|
||||
a=ssrc:2713379498 cname:61992fce-bab5-42a0-ab8c-7112adfb1857
|
||||
a=rtpmap:97 VP8/90000
|
||||
a=rtcp-fb:97 nack
|
||||
a=rtcp-fb:97 nack pli
|
||||
a=rtcp-fb:97 goog-remb
|
||||
a=rtpmap:98 rtx/90000
|
||||
a=fmtp:98 apt=97
|
||||
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=rtpmap:100 rtx/90000
|
||||
a=fmtp:100 apt=99
|
||||
a=rtpmap:101 H264/90000
|
||||
a=rtcp-fb:101 nack
|
||||
a=rtcp-fb:101 nack pli
|
||||
a=rtcp-fb:101 goog-remb
|
||||
a=fmtp:101 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
|
||||
a=rtpmap:102 rtx/90000
|
||||
a=fmtp:102 apt=101
|
||||
a=ice-ufrag:1234
|
||||
a=ice-pwd:1234
|
||||
a=fingerprint:sha-256 70:3A:2D:37:3C:52:96:0E:10:F6:4D:7A:EB:18:38:1B:FD:CA:A5:90:D7:6C:DA:A9:39:76:C9:2F:FB:FF:56:0C
|
||||
a=setup:actpass"""
|
||||
info = parse_info_from_offer(lf2crlf(sdp))
|
||||
assert info.n_expected_camera_tracks == 2
|
||||
assert not info.expected_audio_track
|
||||
assert not info.incoming_audio_track
|
||||
assert not info.incoming_datachannel
|
||||
|
||||
def test_recvonly_audio(self):
|
||||
sdp = """v=0
|
||||
o=- 3910210904 3910210904 IN IP4 0.0.0.0
|
||||
s=-
|
||||
t=0 0
|
||||
a=group:BUNDLE 0
|
||||
a=msid-semantic:WMS *
|
||||
m=audio 9 UDP/TLS/RTP/SAVPF 96 0 8
|
||||
c=IN IP4 0.0.0.0
|
||||
a=recvonly
|
||||
a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level
|
||||
a=mid:0
|
||||
a=msid:eb1d3f1a-569a-465f-b419-319477bfded6 e44eecb2-1a04-4547-97d8-481389f50d5b
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=rtcp-mux
|
||||
a=ssrc:1233332626 cname:ca4dede8-4994-4a6d-9ae3-923b28177ca5
|
||||
a=rtpmap:96 opus/48000/2
|
||||
a=rtpmap:0 PCMU/8000
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=ice-ufrag:1234
|
||||
a=ice-pwd:1234
|
||||
a=fingerprint:sha-256 40:4B:14:CF:70:B8:67:E1:B1:FF:7E:F9:22:6E:60:7D:73:B5:1E:38:4B:10:20:9C:CD:1C:47:02:52:ED:45:25
|
||||
a=setup:actpass"""
|
||||
info = parse_info_from_offer(lf2crlf(sdp))
|
||||
assert info.n_expected_camera_tracks == 0
|
||||
assert info.expected_audio_track
|
||||
assert not info.incoming_audio_track
|
||||
assert not info.incoming_datachannel
|
||||
|
||||
def test_incoming_datachanel(self):
|
||||
sdp = """v=0
|
||||
o=- 3910211092 3910211092 IN IP4 0.0.0.0
|
||||
s=-
|
||||
t=0 0
|
||||
a=group:BUNDLE 0
|
||||
a=msid-semantic:WMS *
|
||||
m=application 9 DTLS/SCTP 5000
|
||||
c=IN IP4 0.0.0.0
|
||||
a=mid:0
|
||||
a=sctpmap:5000 webrtc-datachannel 65535
|
||||
a=max-message-size:65536
|
||||
a=ice-ufrag:1234
|
||||
a=ice-pwd:1234
|
||||
a=fingerprint:sha-256 9B:C0:F3:35:8E:05:A1:15:DB:F8:39:0E:B0:E0:0C:EB:82:E4:B9:26:18:A6:43:2D:B9:9A:23:96:0A:59:B6:58
|
||||
a=setup:actpass"""
|
||||
info = parse_info_from_offer(lf2crlf(sdp))
|
||||
assert info.n_expected_camera_tracks == 0
|
||||
assert not info.expected_audio_track
|
||||
assert not info.incoming_audio_track
|
||||
assert info.incoming_datachannel
|
||||
134
teleoprtc_repo/tests/test_integration.py
Executable file
134
teleoprtc_repo/tests/test_integration.py
Executable file
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import pytest
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from aiortc.mediastreams import AudioStreamTrack, VideoStreamTrack
|
||||
from parameterized import parameterized
|
||||
|
||||
from teleoprtc.builder import WebRTCOfferBuilder, WebRTCAnswerBuilder
|
||||
from teleoprtc.stream import StreamingOffer
|
||||
from teleoprtc.info import parse_info_from_offer
|
||||
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
timeout = asyncio.timeout
|
||||
else:
|
||||
class Timeout:
|
||||
def __init__(self, delay: float):
|
||||
self._delay = delay
|
||||
self._task = None
|
||||
self._timeout_handle = None
|
||||
|
||||
def _timeout(self):
|
||||
if self._task:
|
||||
self._task.cancel()
|
||||
|
||||
async def __aenter__(self):
|
||||
self._task = asyncio.current_task()
|
||||
loop = asyncio.events.get_running_loop()
|
||||
self._timeout_handle = loop.call_later(self._delay, self._timeout)
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
if self._timeout_handle:
|
||||
self._timeout_handle.cancel()
|
||||
if exc_type is asyncio.CancelledError and self._task and self._task.cancelled():
|
||||
raise asyncio.TimeoutError from exc
|
||||
return False
|
||||
|
||||
def timeout(delay):
|
||||
return Timeout(delay)
|
||||
|
||||
|
||||
class SimpleAnswerProvider:
|
||||
def __init__(self):
|
||||
self.stream = None
|
||||
|
||||
async def __call__(self, offer: StreamingOffer):
|
||||
assert self.stream is None, "This may only be called once"
|
||||
|
||||
info = parse_info_from_offer(offer.sdp)
|
||||
|
||||
builder = WebRTCAnswerBuilder(offer.sdp)
|
||||
for cam in offer.video:
|
||||
builder.add_video_stream(cam, VideoStreamTrack())
|
||||
if info.expected_audio_track:
|
||||
builder.add_audio_stream(AudioStreamTrack())
|
||||
if info.incoming_audio_track:
|
||||
builder.offer_to_receive_audio_stream()
|
||||
|
||||
self.stream = builder.stream()
|
||||
answer = await self.stream.start()
|
||||
|
||||
return answer
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestStreamIntegration:
|
||||
@parameterized.expand([
|
||||
# name, recv_cameras, recv_audio, messaging
|
||||
("multi_camera", ["driver", "wideRoad", "road"], False, False),
|
||||
("camera_and_audio", ["driver"], True, False),
|
||||
("camera_and__messaging", ["driver"], False, True),
|
||||
("camera_and_audio_and_messaging", ["driver", "wideRoad", "road"], True, True),
|
||||
])
|
||||
async def test_multi_camera(self, name, cameras, recv_audio, add_messaging):
|
||||
simple_answerer = SimpleAnswerProvider()
|
||||
offer_builder = WebRTCOfferBuilder(simple_answerer)
|
||||
for cam in cameras:
|
||||
offer_builder.offer_to_receive_video_stream(cam)
|
||||
if recv_audio:
|
||||
offer_builder.offer_to_receive_audio_stream()
|
||||
if add_messaging:
|
||||
offer_builder.add_messaging()
|
||||
stream = offer_builder.stream()
|
||||
|
||||
_ = await stream.start()
|
||||
assert stream.is_started
|
||||
|
||||
try:
|
||||
async with timeout(2):
|
||||
await stream.wait_for_connection()
|
||||
except TimeoutError:
|
||||
pytest.fail("Timed out waiting for connection")
|
||||
assert stream.is_connected_and_ready
|
||||
|
||||
assert stream.has_messaging_channel() == add_messaging
|
||||
if stream.has_messaging_channel():
|
||||
channel = stream.get_messaging_channel()
|
||||
assert channel is not None
|
||||
assert channel.readyState == "open"
|
||||
|
||||
assert stream.has_incoming_audio_track() == recv_audio
|
||||
if stream.has_incoming_audio_track():
|
||||
track = stream.get_incoming_audio_track(False)
|
||||
assert track is not None
|
||||
assert track.readyState == "live"
|
||||
assert track.kind == "audio"
|
||||
# test audio recv
|
||||
try:
|
||||
async with timeout(1):
|
||||
await track.recv()
|
||||
except TimeoutError:
|
||||
pytest.fail("Timed out waiting for audio frame")
|
||||
|
||||
for cam in cameras:
|
||||
assert stream.has_incoming_video_track(cam)
|
||||
if stream.has_incoming_video_track(cam):
|
||||
track = stream.get_incoming_video_track(cam, False)
|
||||
assert track is not None
|
||||
assert track.readyState == "live"
|
||||
assert track.kind == "video"
|
||||
# test video recv
|
||||
try:
|
||||
async with timeout(1):
|
||||
await stream.get_incoming_video_track(cam, False).recv()
|
||||
except TimeoutError:
|
||||
pytest.fail("Timed out waiting for video frame")
|
||||
|
||||
await stream.stop()
|
||||
await simple_answerer.stream.stop()
|
||||
assert not stream.is_started
|
||||
assert not stream.is_connected_and_ready
|
||||
153
teleoprtc_repo/tests/test_stream.py
Executable file
153
teleoprtc_repo/tests/test_stream.py
Executable file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import pytest
|
||||
|
||||
import aiortc
|
||||
from aiortc.mediastreams import AudioStreamTrack
|
||||
|
||||
from teleoprtc.builder import WebRTCOfferBuilder, WebRTCAnswerBuilder
|
||||
from teleoprtc.info import parse_info_from_offer
|
||||
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()
|
||||
|
||||
def codec_preference(self):
|
||||
return "H264"
|
||||
|
||||
|
||||
@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
|
||||
|
||||
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_sendonly_audio(self):
|
||||
capture = OfferCapture()
|
||||
builder = WebRTCOfferBuilder(capture)
|
||||
builder.add_audio_stream(AudioStreamTrack())
|
||||
stream = builder.stream()
|
||||
|
||||
try:
|
||||
_ = await stream.start()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
info = parse_info_from_offer(capture.offer.sdp)
|
||||
assert not info.expected_audio_track
|
||||
assert 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
|
||||
|
||||
info = parse_info_from_offer(capture.offer.sdp)
|
||||
assert info.incoming_datachannel
|
||||
|
||||
|
||||
@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()
|
||||
answer = await stream.start()
|
||||
|
||||
sdp_desc = aiortc.sdp.SessionDescription.parse(answer.sdp)
|
||||
video_desc = [m for m in sdp_desc.media if m.kind == "video"][0]
|
||||
codecs = video_desc.rtp.codecs
|
||||
assert codecs[0].mimeType == "video/H264"
|
||||
|
||||
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()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
_ = await stream.start()
|
||||
34
teleoprtc_repo/tests/test_track.py
Executable file
34
teleoprtc_repo/tests/test_track.py
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import pytest
|
||||
|
||||
import aiortc
|
||||
|
||||
from teleoprtc.tracks import video_track_id, parse_video_track_id, TiciVideoStreamTrack, TiciTrackWrapper
|
||||
|
||||
|
||||
class TestTracks:
|
||||
def test_track_id(self):
|
||||
expected_camera_type, expected_track_id = "driver", "test"
|
||||
track_id = video_track_id(expected_camera_type, expected_track_id)
|
||||
camera_type, track_id = parse_video_track_id(track_id)
|
||||
assert expected_camera_type == camera_type
|
||||
assert expected_track_id == track_id
|
||||
|
||||
def test_track_id_invalid(self):
|
||||
with pytest.raises(ValueError):
|
||||
parse_video_track_id("test")
|
||||
|
||||
def test_tici_track_id(self):
|
||||
class VideoStream(TiciVideoStreamTrack):
|
||||
async def recv(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
track = VideoStream("driver", 0.1)
|
||||
camera_type, _ = parse_video_track_id(track.id)
|
||||
assert "driver" == camera_type
|
||||
|
||||
def test_tici_wrapper_id(self):
|
||||
track = TiciTrackWrapper("driver", aiortc.mediastreams.VideoStreamTrack())
|
||||
camera_type, _ = parse_video_track_id(track.id)
|
||||
assert "driver" == camera_type
|
||||
Reference in New Issue
Block a user