sora-sdk 2025.2.0.dev2__tar.gz → 2025.2.1.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sora-sdk might be problematic. Click here for more details.
- {sora_sdk-2025.2.0.dev2/src/sora_sdk.egg-info → sora_sdk-2025.2.1.dev0}/PKG-INFO +1 -1
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/pyproject.toml +3 -3
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/src/sora_sdk/sora_sdk_ext.cpython-311-darwin.so +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0/src/sora_sdk.egg-info}/PKG-INFO +1 -1
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/src/sora_sdk.egg-info/SOURCES.txt +1 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_amd_amf.py +3 -15
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_apple_video_toolbox.py +9 -14
- sora_sdk-2025.2.1.dev0/tests/test_authz_simulcast.py +544 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_intel_vpl.py +5 -10
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_nvidia_video_codec_sdk.py +5 -10
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_openh264.py +0 -147
- sora_sdk-2025.2.1.dev0/tests/test_openh264_simulcast.py +434 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_simulcast.py +19 -23
- sora_sdk-2025.2.0.dev2/tests/test_authz_simulcast.py +0 -205
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/LICENSE +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/MANIFEST.in +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/README.md +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/buildbase.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/pypath.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/run.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/setup.cfg +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/setup.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/src/sora_sdk/__init__.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/src/sora_sdk/py.typed +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/src/sora_sdk/sora_sdk_ext.pyi +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/src/sora_sdk.egg-info/dependency_links.txt +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/src/sora_sdk.egg-info/top_level.txt +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_authz.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_ca_cert.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_capability.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_degradation_preference.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_encoded_transform.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_messaging.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_messaging_header.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_opus.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_re_offer_re_answer_sdp.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_sendonly_recvonly.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_signaling.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_signaling_message.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_signaling_notify.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_sora_disconnect.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_type_disconnect.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_type_switched.py +0 -0
- {sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/tests/test_vad.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sora_sdk"
|
|
3
3
|
authors = [{ name = "Shiguredo Inc.", email = "contact+pypi@shiguredo.jp" }]
|
|
4
|
-
version = "2025.2.
|
|
4
|
+
version = "2025.2.1.dev0"
|
|
5
5
|
description = "WebRTC SFU Sora Python SDK"
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
license = "Apache-2.0"
|
|
@@ -20,14 +20,14 @@ Documentation = "https://sora-python-sdk.shiguredo.jp"
|
|
|
20
20
|
Discord = "https://discord.gg/shiguredo"
|
|
21
21
|
|
|
22
22
|
[build-system]
|
|
23
|
-
requires = ["setuptools==
|
|
23
|
+
requires = ["setuptools==80.0", "wheel==0.45.1"]
|
|
24
24
|
build-backend = "setuptools.build_meta"
|
|
25
25
|
|
|
26
26
|
[tool.uv]
|
|
27
27
|
python-preference = "only-managed"
|
|
28
28
|
dev-dependencies = [
|
|
29
29
|
"nanobind==2.7.0",
|
|
30
|
-
"setuptools==
|
|
30
|
+
"setuptools==80.0",
|
|
31
31
|
"build==1.2.2.post1",
|
|
32
32
|
"wheel==0.45.1",
|
|
33
33
|
"typing-extensions",
|
{sora_sdk-2025.2.0.dev2 → sora_sdk-2025.2.1.dev0}/src/sora_sdk/sora_sdk_ext.cpython-311-darwin.so
RENAMED
|
Binary file
|
|
@@ -238,7 +238,7 @@ def test_amd_amf_simulcast(
|
|
|
238
238
|
)
|
|
239
239
|
sendonly.connect(fake_video=True)
|
|
240
240
|
|
|
241
|
-
time.sleep(
|
|
241
|
+
time.sleep(10)
|
|
242
242
|
|
|
243
243
|
sendonly_stats = sendonly.get_stats()
|
|
244
244
|
|
|
@@ -269,25 +269,13 @@ def test_amd_amf_simulcast(
|
|
|
269
269
|
assert "qualityLimitationDurations" in s
|
|
270
270
|
|
|
271
271
|
# qualityLimitationReason が none で無い場合は安定したテストができない
|
|
272
|
-
|
|
273
|
-
if (
|
|
274
|
-
s["qualityLimitationReason"] != "none"
|
|
275
|
-
and "frameWidth" not in s
|
|
276
|
-
and "frameHeight" not in s
|
|
277
|
-
):
|
|
272
|
+
if s["qualityLimitationReason"] != "none":
|
|
278
273
|
pytest.skip(f"qualityLimitationReason: {s['qualityLimitationReason']}")
|
|
279
274
|
|
|
280
275
|
assert s["rid"] == f"r{i}"
|
|
281
276
|
# simulcast_count が 2 の場合、rid r2 の bytesSent/packetsSent は 0 or 1 になる
|
|
282
277
|
# simulcast_count が 1 の場合、rid r2 と r1 の bytesSent/packetsSent は 0 or 1 になる
|
|
283
278
|
if i < simulcast_count:
|
|
284
|
-
# 1 本になると simulcastEncodingAdapter がなくなる
|
|
285
|
-
# if simulcast_count > 1:
|
|
286
|
-
# assert "SimulcastEncoderAdapter" in s["encoderImplementation"]
|
|
287
|
-
|
|
288
|
-
# targetBitrate が指定したビットレートの 90% 以上、100% 以下に収まることを確認
|
|
289
|
-
expected_bitrate = video_bit_rate * 1000
|
|
290
|
-
|
|
291
279
|
assert s["bytesSent"] > 1000
|
|
292
280
|
assert s["packetsSent"] > 5
|
|
293
281
|
|
|
@@ -300,7 +288,7 @@ def test_amd_amf_simulcast(
|
|
|
300
288
|
print(
|
|
301
289
|
s["rid"],
|
|
302
290
|
video_codec_type,
|
|
303
|
-
|
|
291
|
+
video_bit_rate * 1000,
|
|
304
292
|
s["targetBitrate"],
|
|
305
293
|
expected_implementation,
|
|
306
294
|
encoder_implementation,
|
|
@@ -143,16 +143,16 @@ def test_apple_video_toolbox_sendonly_recvonly(setup, video_codec_type):
|
|
|
143
143
|
("H264", "VideoToolbox", 960, 540, 3),
|
|
144
144
|
("H265", "VideoToolbox", 960, 540, 3),
|
|
145
145
|
# 360p
|
|
146
|
-
("H264", "VideoToolbox", 640, 360, 2),
|
|
147
|
-
("H265", "VideoToolbox", 640, 360, 2),
|
|
146
|
+
# ("H264", "VideoToolbox", 640, 360, 2),
|
|
147
|
+
# ("H265", "VideoToolbox", 640, 360, 2),
|
|
148
148
|
# 270p
|
|
149
|
-
("H264", "VideoToolbox", 480, 270, 2),
|
|
150
|
-
("H265", "VideoToolbox", 480, 270, 2),
|
|
149
|
+
# ("H264", "VideoToolbox", 480, 270, 2),
|
|
150
|
+
# ("H265", "VideoToolbox", 480, 270, 2),
|
|
151
151
|
# 180p
|
|
152
|
-
("H264", "VideoToolbox", 320, 180, 1),
|
|
153
|
-
("H265", "VideoToolbox", 320, 180, 1),
|
|
152
|
+
# ("H264", "VideoToolbox", 320, 180, 1),
|
|
153
|
+
# ("H265", "VideoToolbox", 320, 180, 1),
|
|
154
154
|
# 135p
|
|
155
|
-
("H265", "VideoToolbox", 240, 135, 1),
|
|
155
|
+
# ("H265", "VideoToolbox", 240, 135, 1),
|
|
156
156
|
],
|
|
157
157
|
)
|
|
158
158
|
def test_apple_video_toolbox_simulcast(
|
|
@@ -186,7 +186,7 @@ def test_apple_video_toolbox_simulcast(
|
|
|
186
186
|
)
|
|
187
187
|
sendonly.connect(fake_video=True)
|
|
188
188
|
|
|
189
|
-
time.sleep(
|
|
189
|
+
time.sleep(10)
|
|
190
190
|
|
|
191
191
|
sendonly_stats = sendonly.get_stats()
|
|
192
192
|
|
|
@@ -217,12 +217,7 @@ def test_apple_video_toolbox_simulcast(
|
|
|
217
217
|
assert "qualityLimitationDurations" in s
|
|
218
218
|
|
|
219
219
|
# qualityLimitationReason が none で無い場合は安定したテストができない
|
|
220
|
-
|
|
221
|
-
if (
|
|
222
|
-
s["qualityLimitationReason"] != "none"
|
|
223
|
-
and "frameWidth" not in s
|
|
224
|
-
and "frameHeight" not in s
|
|
225
|
-
):
|
|
220
|
+
if s["qualityLimitationReason"] != "none":
|
|
226
221
|
pytest.skip(f"qualityLimitationReason: {s['qualityLimitationReason']}")
|
|
227
222
|
|
|
228
223
|
assert s["rid"] == f"r{i}"
|
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import time
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
import jwt
|
|
6
|
+
import pytest
|
|
7
|
+
from client import SoraClient, SoraRole
|
|
8
|
+
from simulcast import default_video_bit_rate, expect_target_bitrate
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.parametrize(
|
|
12
|
+
(
|
|
13
|
+
"video_codec_type",
|
|
14
|
+
"encoder_implementation",
|
|
15
|
+
"video_width",
|
|
16
|
+
"video_height",
|
|
17
|
+
),
|
|
18
|
+
[
|
|
19
|
+
# 360p
|
|
20
|
+
("VP8", "libvpx", 640, 360),
|
|
21
|
+
("VP9", "libvpx", 640, 360),
|
|
22
|
+
("AV1", "libaom", 640, 360),
|
|
23
|
+
# 270p
|
|
24
|
+
("VP8", "libvpx", 480, 270),
|
|
25
|
+
("VP9", "libvpx", 480, 270),
|
|
26
|
+
("AV1", "libaom", 480, 270),
|
|
27
|
+
],
|
|
28
|
+
)
|
|
29
|
+
def test_authz_simulcast_r2_active_false(
|
|
30
|
+
setup,
|
|
31
|
+
video_codec_type,
|
|
32
|
+
encoder_implementation,
|
|
33
|
+
video_width,
|
|
34
|
+
video_height,
|
|
35
|
+
):
|
|
36
|
+
signaling_urls = setup.get("signaling_urls")
|
|
37
|
+
channel_id_prefix = setup.get("channel_id_prefix")
|
|
38
|
+
secret = setup.get("secret")
|
|
39
|
+
|
|
40
|
+
channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"
|
|
41
|
+
|
|
42
|
+
video_bit_rate = default_video_bit_rate(video_codec_type, video_width, video_height)
|
|
43
|
+
|
|
44
|
+
simulcast_encodings = [
|
|
45
|
+
{
|
|
46
|
+
"rid": "r0",
|
|
47
|
+
"active": True,
|
|
48
|
+
"scaleResolutionDownBy": 2,
|
|
49
|
+
"scalabilityMode": "L1T1",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"rid": "r1",
|
|
53
|
+
"active": True,
|
|
54
|
+
"scaleResolutionDownBy": 1,
|
|
55
|
+
"scalabilityMode": "L1T1",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"rid": "r2",
|
|
59
|
+
"active": False,
|
|
60
|
+
},
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
access_token = jwt.encode(
|
|
64
|
+
{
|
|
65
|
+
"channel_id": channel_id,
|
|
66
|
+
"video": True,
|
|
67
|
+
"video_codec_type": video_codec_type,
|
|
68
|
+
"video_bit_rate": video_bit_rate,
|
|
69
|
+
"simulcast": True,
|
|
70
|
+
"simulcast_encodings": simulcast_encodings,
|
|
71
|
+
# 現在時刻 + 300 秒 (5分)
|
|
72
|
+
"exp": int(time.time()) + 300,
|
|
73
|
+
},
|
|
74
|
+
secret,
|
|
75
|
+
algorithm="HS256",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
sendonly = SoraClient(
|
|
79
|
+
signaling_urls,
|
|
80
|
+
SoraRole.SENDONLY,
|
|
81
|
+
channel_id,
|
|
82
|
+
audio=False,
|
|
83
|
+
video=True,
|
|
84
|
+
metadata={"access_token": access_token},
|
|
85
|
+
video_width=video_width,
|
|
86
|
+
video_height=video_height,
|
|
87
|
+
)
|
|
88
|
+
sendonly.connect(fake_video=True)
|
|
89
|
+
|
|
90
|
+
time.sleep(10)
|
|
91
|
+
|
|
92
|
+
# "type": "offer" の SDP で Simulcast があるかどうか
|
|
93
|
+
assert sendonly.offer_message is not None
|
|
94
|
+
assert sendonly.offer_message["sdp"] is not None
|
|
95
|
+
assert video_codec_type in sendonly.offer_message["sdp"]
|
|
96
|
+
assert "a=simulcast:recv r0;r1;~r2" in sendonly.offer_message["sdp"]
|
|
97
|
+
|
|
98
|
+
assert "encodings" in sendonly.offer_message
|
|
99
|
+
assert len(sendonly.offer_message["encodings"]) == 3
|
|
100
|
+
|
|
101
|
+
assert sendonly.offer_message["encodings"][0]["rid"] == simulcast_encodings[0]["rid"]
|
|
102
|
+
assert sendonly.offer_message["encodings"][1]["rid"] == simulcast_encodings[1]["rid"]
|
|
103
|
+
assert sendonly.offer_message["encodings"][2]["rid"] == simulcast_encodings[2]["rid"]
|
|
104
|
+
|
|
105
|
+
assert sendonly.offer_message["encodings"][0]["active"] == simulcast_encodings[0]["active"]
|
|
106
|
+
assert sendonly.offer_message["encodings"][1]["active"] == simulcast_encodings[1]["active"]
|
|
107
|
+
assert sendonly.offer_message["encodings"][2]["active"] == simulcast_encodings[2]["active"]
|
|
108
|
+
|
|
109
|
+
assert (
|
|
110
|
+
sendonly.offer_message["encodings"][0]["scaleResolutionDownBy"]
|
|
111
|
+
== simulcast_encodings[0]["scaleResolutionDownBy"]
|
|
112
|
+
)
|
|
113
|
+
assert (
|
|
114
|
+
sendonly.offer_message["encodings"][1]["scaleResolutionDownBy"]
|
|
115
|
+
== simulcast_encodings[1]["scaleResolutionDownBy"]
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
assert (
|
|
119
|
+
sendonly.offer_message["encodings"][0]["scalabilityMode"]
|
|
120
|
+
== simulcast_encodings[0]["scalabilityMode"]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
assert (
|
|
124
|
+
sendonly.offer_message["encodings"][1]["scalabilityMode"]
|
|
125
|
+
== simulcast_encodings[1]["scalabilityMode"]
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# "type": "answer" の SDP で Simulcast があるかどうか
|
|
129
|
+
assert sendonly.answer_message is not None
|
|
130
|
+
assert "sdp" in sendonly.answer_message
|
|
131
|
+
assert "a=simulcast:send r0;r1;~r2" in sendonly.answer_message["sdp"]
|
|
132
|
+
|
|
133
|
+
sendonly_stats = sendonly.get_stats()
|
|
134
|
+
sendonly.disconnect()
|
|
135
|
+
|
|
136
|
+
# codec が無かったら StopIteration 例外が上がる
|
|
137
|
+
sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
|
|
138
|
+
assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
|
|
139
|
+
|
|
140
|
+
# 複数の outbound-rtp 統計情報を取得
|
|
141
|
+
outbound_rtp_stats = [
|
|
142
|
+
s
|
|
143
|
+
for s in sendonly_stats
|
|
144
|
+
if s.get("type") == "outbound-rtp" and s.get("kind") == "video" and s.get("active") is True
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
# active は 2 本だけ
|
|
148
|
+
assert len(outbound_rtp_stats) == 2
|
|
149
|
+
|
|
150
|
+
# rid でソート
|
|
151
|
+
sorted_stats = sorted(outbound_rtp_stats, key=lambda x: x.get("rid", ""))
|
|
152
|
+
|
|
153
|
+
for i, s in enumerate(sorted_stats):
|
|
154
|
+
assert s["rid"] == f"r{i}"
|
|
155
|
+
assert s["kind"] == "video"
|
|
156
|
+
|
|
157
|
+
assert encoder_implementation in s.get("encoderImplementation")
|
|
158
|
+
if video_codec_type in ["AV1"]:
|
|
159
|
+
assert "SimulcastEncoderAdapter" in s.get("encoderImplementation")
|
|
160
|
+
|
|
161
|
+
if s["qualityLimitationReason"] != "none":
|
|
162
|
+
pytest.skip(f"qualityLimitationReason: {s['qualityLimitationReason']}")
|
|
163
|
+
|
|
164
|
+
assert s["keyFramesEncoded"] > 0
|
|
165
|
+
assert s["bytesSent"] > 500
|
|
166
|
+
assert s["packetsSent"] > 5
|
|
167
|
+
|
|
168
|
+
# assert s["frameWidth"] >= video_width
|
|
169
|
+
# assert s["frameHeight"] >= video_height
|
|
170
|
+
|
|
171
|
+
assert s["targetBitrate"] >= expect_target_bitrate(
|
|
172
|
+
video_codec_type, s["frameWidth"], s["frameHeight"]
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
assert "scalabilityMode" in s
|
|
176
|
+
assert s["scalabilityMode"] == "L1T1"
|
|
177
|
+
|
|
178
|
+
print(
|
|
179
|
+
s["rid"],
|
|
180
|
+
video_codec_type,
|
|
181
|
+
s["encoderImplementation"],
|
|
182
|
+
s["scalabilityMode"],
|
|
183
|
+
video_bit_rate * 1000,
|
|
184
|
+
s["targetBitrate"],
|
|
185
|
+
s["frameWidth"],
|
|
186
|
+
s["frameHeight"],
|
|
187
|
+
s["bytesSent"],
|
|
188
|
+
s["packetsSent"],
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@pytest.mark.parametrize(
|
|
193
|
+
(
|
|
194
|
+
"video_codec_type",
|
|
195
|
+
"encoder_implementation",
|
|
196
|
+
"video_width",
|
|
197
|
+
"video_height",
|
|
198
|
+
),
|
|
199
|
+
[
|
|
200
|
+
# 180p
|
|
201
|
+
("VP8", "libvpx", 320, 180),
|
|
202
|
+
("VP9", "libvpx", 320, 180),
|
|
203
|
+
("AV1", "libaom", 320, 180),
|
|
204
|
+
# 135p
|
|
205
|
+
("VP8", "libvpx", 240, 135),
|
|
206
|
+
("VP9", "libvpx", 240, 135),
|
|
207
|
+
("AV1", "libaom", 240, 135),
|
|
208
|
+
],
|
|
209
|
+
)
|
|
210
|
+
def test_authz_simulcast_r2_and_r1_active_false(
|
|
211
|
+
setup,
|
|
212
|
+
video_codec_type,
|
|
213
|
+
encoder_implementation,
|
|
214
|
+
video_width,
|
|
215
|
+
video_height,
|
|
216
|
+
):
|
|
217
|
+
signaling_urls = setup.get("signaling_urls")
|
|
218
|
+
channel_id_prefix = setup.get("channel_id_prefix")
|
|
219
|
+
secret = setup.get("secret")
|
|
220
|
+
|
|
221
|
+
channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"
|
|
222
|
+
video_bit_rate = default_video_bit_rate(video_codec_type, video_width, video_height)
|
|
223
|
+
|
|
224
|
+
simulcast_encodings = [
|
|
225
|
+
{
|
|
226
|
+
"rid": "r0",
|
|
227
|
+
"active": True,
|
|
228
|
+
"scaleResolutionDownBy": 1,
|
|
229
|
+
"scalabilityMode": "L1T1",
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"rid": "r1",
|
|
233
|
+
"active": False,
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"rid": "r2",
|
|
237
|
+
"active": False,
|
|
238
|
+
},
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
access_token = jwt.encode(
|
|
242
|
+
{
|
|
243
|
+
"channel_id": channel_id,
|
|
244
|
+
"video": True,
|
|
245
|
+
"video_codec_type": video_codec_type,
|
|
246
|
+
"video_bit_rate": video_bit_rate,
|
|
247
|
+
"simulcast": True,
|
|
248
|
+
"simulcast_encodings": simulcast_encodings,
|
|
249
|
+
# 現在時刻 + 300 秒 (5分)
|
|
250
|
+
"exp": int(time.time()) + 300,
|
|
251
|
+
},
|
|
252
|
+
secret,
|
|
253
|
+
algorithm="HS256",
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
sendonly = SoraClient(
|
|
257
|
+
signaling_urls,
|
|
258
|
+
SoraRole.SENDONLY,
|
|
259
|
+
channel_id,
|
|
260
|
+
audio=False,
|
|
261
|
+
video=True,
|
|
262
|
+
metadata={"access_token": access_token},
|
|
263
|
+
video_width=video_width,
|
|
264
|
+
video_height=video_height,
|
|
265
|
+
)
|
|
266
|
+
sendonly.connect(fake_video=True)
|
|
267
|
+
|
|
268
|
+
time.sleep(10)
|
|
269
|
+
|
|
270
|
+
# "type": "offer" の SDP で Simulcast があるかどうか
|
|
271
|
+
assert sendonly.offer_message is not None
|
|
272
|
+
assert sendonly.offer_message["sdp"] is not None
|
|
273
|
+
assert video_codec_type in sendonly.offer_message["sdp"]
|
|
274
|
+
assert "a=simulcast:recv r0;~r1;~r2" in sendonly.offer_message["sdp"]
|
|
275
|
+
|
|
276
|
+
assert "encodings" in sendonly.offer_message
|
|
277
|
+
assert len(sendonly.offer_message["encodings"]) == 3
|
|
278
|
+
|
|
279
|
+
assert sendonly.offer_message["encodings"][0]["rid"] == simulcast_encodings[0]["rid"]
|
|
280
|
+
assert sendonly.offer_message["encodings"][1]["rid"] == simulcast_encodings[1]["rid"]
|
|
281
|
+
assert sendonly.offer_message["encodings"][2]["rid"] == simulcast_encodings[2]["rid"]
|
|
282
|
+
|
|
283
|
+
assert sendonly.offer_message["encodings"][0]["active"] == simulcast_encodings[0]["active"]
|
|
284
|
+
assert sendonly.offer_message["encodings"][1]["active"] == simulcast_encodings[1]["active"]
|
|
285
|
+
assert sendonly.offer_message["encodings"][2]["active"] == simulcast_encodings[2]["active"]
|
|
286
|
+
|
|
287
|
+
assert (
|
|
288
|
+
sendonly.offer_message["encodings"][0]["scaleResolutionDownBy"]
|
|
289
|
+
== simulcast_encodings[0]["scaleResolutionDownBy"]
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
assert (
|
|
293
|
+
sendonly.offer_message["encodings"][0]["scalabilityMode"]
|
|
294
|
+
== simulcast_encodings[0]["scalabilityMode"]
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# "type": "answer" の SDP で Simulcast があるかどうか
|
|
298
|
+
assert sendonly.answer_message is not None
|
|
299
|
+
assert "sdp" in sendonly.answer_message
|
|
300
|
+
assert "a=simulcast:send r0;~r1;~r2" in sendonly.answer_message["sdp"]
|
|
301
|
+
|
|
302
|
+
sendonly_stats = sendonly.get_stats()
|
|
303
|
+
sendonly.disconnect()
|
|
304
|
+
|
|
305
|
+
# codec が無かったら StopIteration 例外が上がる
|
|
306
|
+
sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
|
|
307
|
+
assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
|
|
308
|
+
|
|
309
|
+
# 複数の outbound-rtp 統計情報を取得
|
|
310
|
+
outbound_rtp_stats = [
|
|
311
|
+
s
|
|
312
|
+
for s in sendonly_stats
|
|
313
|
+
if s.get("type") == "outbound-rtp" and s.get("kind") == "video" and s.get("active") is True
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
assert len(outbound_rtp_stats) == 1
|
|
317
|
+
|
|
318
|
+
s = outbound_rtp_stats[0]
|
|
319
|
+
|
|
320
|
+
assert s["rid"] == "r0"
|
|
321
|
+
assert s["kind"] == "video"
|
|
322
|
+
|
|
323
|
+
assert encoder_implementation in s["encoderImplementation"]
|
|
324
|
+
print(s["encoderImplementation"])
|
|
325
|
+
|
|
326
|
+
if s["qualityLimitationReason"] != "none":
|
|
327
|
+
pytest.skip(f"qualityLimitationReason: {s['qualityLimitationReason']}")
|
|
328
|
+
|
|
329
|
+
assert s["keyFramesEncoded"] > 0
|
|
330
|
+
assert s["bytesSent"] > 500
|
|
331
|
+
assert s["packetsSent"] > 5
|
|
332
|
+
|
|
333
|
+
assert s["targetBitrate"] >= expect_target_bitrate(
|
|
334
|
+
video_codec_type, s["frameWidth"], s["frameHeight"]
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
assert "scalabilityMode" in s
|
|
338
|
+
assert s["scalabilityMode"] == "L1T1"
|
|
339
|
+
|
|
340
|
+
print(
|
|
341
|
+
s["rid"],
|
|
342
|
+
video_codec_type,
|
|
343
|
+
s["encoderImplementation"],
|
|
344
|
+
s["scalabilityMode"],
|
|
345
|
+
video_bit_rate * 1000,
|
|
346
|
+
s["targetBitrate"],
|
|
347
|
+
s["frameWidth"],
|
|
348
|
+
s["frameHeight"],
|
|
349
|
+
s["bytesSent"],
|
|
350
|
+
s["packetsSent"],
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@pytest.mark.skipif(sys.platform == "darwin", reason="Apple では SW コーデックは動作させない")
|
|
355
|
+
@pytest.mark.parametrize(
|
|
356
|
+
(
|
|
357
|
+
"video_codec_type",
|
|
358
|
+
"encoder_implementation",
|
|
359
|
+
"video_bit_rate",
|
|
360
|
+
"video_width",
|
|
361
|
+
"video_height",
|
|
362
|
+
),
|
|
363
|
+
[
|
|
364
|
+
# どうやら scaleResolutionDownTo を指定すると規定されたテーブルのビットレートでは足りない模様
|
|
365
|
+
("VP8", "libvpx", 1200 * 3, 960, 540),
|
|
366
|
+
("VP9", "libvpx", 879 * 3, 960, 540),
|
|
367
|
+
("AV1", "libaom", 879 * 3, 960, 540),
|
|
368
|
+
],
|
|
369
|
+
)
|
|
370
|
+
def test_authz_simulcast_scale_resolution_down_to(
|
|
371
|
+
setup,
|
|
372
|
+
video_codec_type,
|
|
373
|
+
encoder_implementation,
|
|
374
|
+
video_bit_rate,
|
|
375
|
+
video_width,
|
|
376
|
+
video_height,
|
|
377
|
+
):
|
|
378
|
+
signaling_urls = setup.get("signaling_urls")
|
|
379
|
+
channel_id_prefix = setup.get("channel_id_prefix")
|
|
380
|
+
secret = setup.get("secret")
|
|
381
|
+
|
|
382
|
+
channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"
|
|
383
|
+
|
|
384
|
+
simulcast_encodings = [
|
|
385
|
+
{
|
|
386
|
+
"rid": "r0",
|
|
387
|
+
"active": True,
|
|
388
|
+
"scaleResolutionDownTo": {"maxWidth": 640, "maxHeight": 360},
|
|
389
|
+
"scalabilityMode": "L1T1",
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
"rid": "r1",
|
|
393
|
+
"active": True,
|
|
394
|
+
"scaleResolutionDownTo": {"maxWidth": 640, "maxHeight": 360},
|
|
395
|
+
"scalabilityMode": "L1T1",
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"rid": "r2",
|
|
399
|
+
"active": True,
|
|
400
|
+
"scaleResolutionDownTo": {"maxWidth": 640, "maxHeight": 360},
|
|
401
|
+
"scalabilityMode": "L1T1",
|
|
402
|
+
},
|
|
403
|
+
]
|
|
404
|
+
|
|
405
|
+
access_token = jwt.encode(
|
|
406
|
+
{
|
|
407
|
+
"channel_id": channel_id,
|
|
408
|
+
"video": True,
|
|
409
|
+
"video_codec_type": video_codec_type,
|
|
410
|
+
"video_bit_rate": video_bit_rate,
|
|
411
|
+
"simulcast": True,
|
|
412
|
+
"simulcast_encodings": simulcast_encodings,
|
|
413
|
+
# 現在時刻 + 300 秒 (5分)
|
|
414
|
+
"exp": int(time.time()) + 300,
|
|
415
|
+
},
|
|
416
|
+
secret,
|
|
417
|
+
algorithm="HS256",
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
sendonly = SoraClient(
|
|
421
|
+
signaling_urls,
|
|
422
|
+
SoraRole.SENDONLY,
|
|
423
|
+
channel_id,
|
|
424
|
+
audio=False,
|
|
425
|
+
video=True,
|
|
426
|
+
metadata={"access_token": access_token},
|
|
427
|
+
video_width=video_width,
|
|
428
|
+
video_height=video_height,
|
|
429
|
+
)
|
|
430
|
+
sendonly.connect(fake_video=True)
|
|
431
|
+
|
|
432
|
+
time.sleep(10)
|
|
433
|
+
|
|
434
|
+
# "type": "offer" の SDP で Simulcast があるかどうか
|
|
435
|
+
assert sendonly.offer_message is not None
|
|
436
|
+
assert sendonly.offer_message["sdp"] is not None
|
|
437
|
+
assert video_codec_type in sendonly.offer_message["sdp"]
|
|
438
|
+
assert "a=simulcast:recv r0;r1;r2" in sendonly.offer_message["sdp"]
|
|
439
|
+
|
|
440
|
+
assert "encodings" in sendonly.offer_message
|
|
441
|
+
assert len(sendonly.offer_message["encodings"]) == 3
|
|
442
|
+
|
|
443
|
+
assert sendonly.offer_message["encodings"][0]["rid"] == simulcast_encodings[0]["rid"]
|
|
444
|
+
assert sendonly.offer_message["encodings"][1]["rid"] == simulcast_encodings[1]["rid"]
|
|
445
|
+
assert sendonly.offer_message["encodings"][2]["rid"] == simulcast_encodings[2]["rid"]
|
|
446
|
+
|
|
447
|
+
assert sendonly.offer_message["encodings"][0]["active"] == simulcast_encodings[0]["active"]
|
|
448
|
+
assert sendonly.offer_message["encodings"][1]["active"] == simulcast_encodings[1]["active"]
|
|
449
|
+
assert sendonly.offer_message["encodings"][2]["active"] == simulcast_encodings[2]["active"]
|
|
450
|
+
|
|
451
|
+
assert (
|
|
452
|
+
sendonly.offer_message["encodings"][0]["scaleResolutionDownTo"]["maxWidth"]
|
|
453
|
+
== simulcast_encodings[0]["scaleResolutionDownTo"]["maxWidth"]
|
|
454
|
+
)
|
|
455
|
+
assert (
|
|
456
|
+
sendonly.offer_message["encodings"][1]["scaleResolutionDownTo"]["maxWidth"]
|
|
457
|
+
== simulcast_encodings[1]["scaleResolutionDownTo"]["maxWidth"]
|
|
458
|
+
)
|
|
459
|
+
assert (
|
|
460
|
+
sendonly.offer_message["encodings"][2]["scaleResolutionDownTo"]["maxWidth"]
|
|
461
|
+
== simulcast_encodings[2]["scaleResolutionDownTo"]["maxWidth"]
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
assert (
|
|
465
|
+
sendonly.offer_message["encodings"][0]["scalabilityMode"]
|
|
466
|
+
== simulcast_encodings[0]["scalabilityMode"]
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
assert (
|
|
470
|
+
sendonly.offer_message["encodings"][1]["scalabilityMode"]
|
|
471
|
+
== simulcast_encodings[1]["scalabilityMode"]
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
assert (
|
|
475
|
+
sendonly.offer_message["encodings"][2]["scalabilityMode"]
|
|
476
|
+
== simulcast_encodings[2]["scalabilityMode"]
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
# "type": "answer" の SDP で Simulcast があるかどうか
|
|
480
|
+
assert sendonly.answer_message is not None
|
|
481
|
+
assert "sdp" in sendonly.answer_message
|
|
482
|
+
assert "a=simulcast:send r0;r1;r2" in sendonly.answer_message["sdp"]
|
|
483
|
+
|
|
484
|
+
sendonly_stats = sendonly.get_stats()
|
|
485
|
+
sendonly.disconnect()
|
|
486
|
+
|
|
487
|
+
# codec が無かったら StopIteration 例外が上がる
|
|
488
|
+
sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
|
|
489
|
+
assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
|
|
490
|
+
|
|
491
|
+
# 複数の outbound-rtp 統計情報を取得
|
|
492
|
+
outbound_rtp_stats = [
|
|
493
|
+
s for s in sendonly_stats if s.get("type") == "outbound-rtp" and s.get("kind") == "video"
|
|
494
|
+
]
|
|
495
|
+
# simulcast_count に関係なく統計情報はかならず 3 本出力される
|
|
496
|
+
# これは SDP で rid で ~r0 とかやる減るはず
|
|
497
|
+
assert len(outbound_rtp_stats) == 3
|
|
498
|
+
|
|
499
|
+
# rid でソート
|
|
500
|
+
sorted_stats = sorted(outbound_rtp_stats, key=lambda x: x.get("rid", ""))
|
|
501
|
+
|
|
502
|
+
for i, s in enumerate(sorted_stats):
|
|
503
|
+
assert s["rid"] == f"r{i}"
|
|
504
|
+
assert s["kind"] == "video"
|
|
505
|
+
|
|
506
|
+
# VP8 の場合は scaleResolutionDownTo を指定すると SimulcastEncoderAdapter が無くなる
|
|
507
|
+
# TODO: 念のため他の挙動も確認すること
|
|
508
|
+
if video_codec_type == "VP9":
|
|
509
|
+
assert "SimulcastEncoderAdapter" in s["encoderImplementation"]
|
|
510
|
+
assert encoder_implementation in s["encoderImplementation"]
|
|
511
|
+
|
|
512
|
+
if s["qualityLimitationReason"] != "none":
|
|
513
|
+
pytest.skip(f"qualityLimitationReason: {s['qualityLimitationReason']}")
|
|
514
|
+
|
|
515
|
+
assert s["keyFramesEncoded"] > 0
|
|
516
|
+
assert s["bytesSent"] > 500
|
|
517
|
+
assert s["packetsSent"] > 5
|
|
518
|
+
|
|
519
|
+
assert s["frameWidth"] == 640
|
|
520
|
+
assert s["frameHeight"] == 352
|
|
521
|
+
|
|
522
|
+
assert s["targetBitrate"] >= expect_target_bitrate(
|
|
523
|
+
video_codec_type, s["frameWidth"], s["frameHeight"]
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
scalability_mode = None
|
|
527
|
+
if "scalabilityMode" in s:
|
|
528
|
+
assert s["scalabilityMode"] == "L1T1"
|
|
529
|
+
scalability_mode = s["scalabilityMode"]
|
|
530
|
+
|
|
531
|
+
# targetBitrate が指定したビットレートの 90% 以上、100% 以下に収まることを確認
|
|
532
|
+
expected_bitrate = video_bit_rate * 1000
|
|
533
|
+
print(
|
|
534
|
+
s["rid"],
|
|
535
|
+
video_codec_type,
|
|
536
|
+
s["encoderImplementation"],
|
|
537
|
+
scalability_mode,
|
|
538
|
+
expected_bitrate,
|
|
539
|
+
s["targetBitrate"],
|
|
540
|
+
s["frameWidth"],
|
|
541
|
+
s["frameHeight"],
|
|
542
|
+
s["bytesSent"],
|
|
543
|
+
s["packetsSent"],
|
|
544
|
+
)
|