sora-sdk 2025.1.0.dev10__tar.gz → 2025.1.0.dev12__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.

Files changed (43) hide show
  1. {sora_sdk-2025.1.0.dev10/src/sora_sdk.egg-info → sora_sdk-2025.1.0.dev12}/PKG-INFO +1 -1
  2. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/pyproject.toml +1 -1
  3. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/src/sora_sdk/sora_sdk_ext.cpython-311-darwin.so +0 -0
  4. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12/src/sora_sdk.egg-info}/PKG-INFO +1 -1
  5. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_apple_video_toolbox.py +229 -87
  6. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_authz_simulcast.py +12 -5
  7. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_intel_vpl.py +27 -12
  8. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_nvidia_video_codec_sdk.py +133 -54
  9. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/LICENSE +0 -0
  10. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/MANIFEST.in +0 -0
  11. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/NOTICE.md +0 -0
  12. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/README.md +0 -0
  13. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/buildbase.py +0 -0
  14. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/pypath.py +0 -0
  15. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/run.py +0 -0
  16. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/setup.cfg +0 -0
  17. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/setup.py +0 -0
  18. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/src/sora_sdk/__init__.py +0 -0
  19. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/src/sora_sdk/py.typed +0 -0
  20. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/src/sora_sdk/sora_sdk_ext.pyi +0 -0
  21. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/src/sora_sdk.egg-info/SOURCES.txt +0 -0
  22. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/src/sora_sdk.egg-info/dependency_links.txt +0 -0
  23. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/src/sora_sdk.egg-info/top_level.txt +0 -0
  24. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_authz.py +0 -0
  25. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_ca_cert.py +0 -0
  26. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_capability.py +0 -0
  27. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_codec_preference.py +0 -0
  28. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_degradation_preference.py +0 -0
  29. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_encoded_transform.py +0 -0
  30. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_messaging.py +0 -0
  31. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_messaging_header.py +0 -0
  32. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_openh264.py +0 -0
  33. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_opus.py +0 -0
  34. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_re_offer_re_answer_sdp.py +0 -0
  35. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_sendonly_recvonly.py +0 -0
  36. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_signaling.py +0 -0
  37. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_signaling_message.py +0 -0
  38. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_signaling_notify.py +0 -0
  39. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_simulcast.py +0 -0
  40. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_sora_disconnect.py +0 -0
  41. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_type_disconnect.py +0 -0
  42. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_type_switched.py +0 -0
  43. {sora_sdk-2025.1.0.dev10 → sora_sdk-2025.1.0.dev12}/tests/test_vad.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: sora_sdk
3
- Version: 2025.1.0.dev10
3
+ Version: 2025.1.0.dev12
4
4
  Summary: WebRTC SFU Sora Python SDK
5
5
  Home-page: https://github.com/shiguredo/sora-python-sdk
6
6
  Author-email: "Shiguredo Inc." <contact+pypi@shiguredo.jp>
@@ -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.1.0.dev10"
4
+ version = "2025.1.0.dev12"
5
5
  description = "WebRTC SFU Sora Python SDK"
6
6
  readme = "README.md"
7
7
  license = { file = "LICENSE" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: sora_sdk
3
- Version: 2025.1.0.dev10
3
+ Version: 2025.1.0.dev12
4
4
  Summary: WebRTC SFU Sora Python SDK
5
5
  Home-page: https://github.com/shiguredo/sora-python-sdk
6
6
  Author-email: "Shiguredo Inc." <contact+pypi@shiguredo.jp>
@@ -1,22 +1,21 @@
1
+ import os
1
2
  import sys
2
3
  import time
3
4
  import uuid
4
5
 
6
+ import jwt
5
7
  import pytest
6
8
  from client import SoraClient, SoraRole
7
9
 
8
- """
9
- GitHub Actions で Video Toolbox を送受信で利用しようとするとエラーになるので、
10
- テストを sendonly のみに絞っている
11
- """
12
10
 
13
-
14
- @pytest.mark.skipif(sys.platform != "darwin", reason="macOS でのみ実行する")
11
+ @pytest.mark.skipif(
12
+ os.environ.get("APPLE_VIDEO_TOOLBOX") is None, reason="Apple Video Toolbox でのみ実行する"
13
+ )
15
14
  @pytest.mark.parametrize(
16
15
  "video_codec_type",
17
16
  ["H264", "H265"],
18
17
  )
19
- def test_macos_video_hwa_sendonly(setup, video_codec_type):
18
+ def test_apple_video_toolbox_sendonly(setup, video_codec_type):
20
19
  signaling_urls = setup.get("signaling_urls")
21
20
  channel_id_prefix = setup.get("channel_id_prefix")
22
21
  metadata = setup.get("metadata")
@@ -57,7 +56,73 @@ def test_macos_video_hwa_sendonly(setup, video_codec_type):
57
56
  assert outbound_rtp_stats["packetsSent"] > 0
58
57
 
59
58
 
60
- @pytest.mark.skipif(sys.platform != "darwin", reason="macOS でのみ実行する")
59
+ @pytest.mark.skipif(
60
+ os.environ.get("APPLE_VIDEO_TOOLBOX") is None, reason="Apple Video Toolbox でのみ実行する"
61
+ )
62
+ @pytest.mark.parametrize(
63
+ "video_codec_type",
64
+ ["H264", "H265"],
65
+ )
66
+ def test_apple_video_toolbox_sendonly_recvonly(setup, video_codec_type):
67
+ signaling_urls = setup.get("signaling_urls")
68
+ channel_id_prefix = setup.get("channel_id_prefix")
69
+ metadata = setup.get("metadata")
70
+
71
+ channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"
72
+
73
+ sendonly = SoraClient(
74
+ signaling_urls,
75
+ SoraRole.SENDONLY,
76
+ channel_id,
77
+ audio=False,
78
+ video=True,
79
+ video_codec_type=video_codec_type,
80
+ metadata=metadata,
81
+ )
82
+ sendonly.connect(fake_video=True)
83
+
84
+ recvonly = SoraClient(
85
+ signaling_urls,
86
+ SoraRole.RECVONLY,
87
+ channel_id,
88
+ metadata=metadata,
89
+ )
90
+ recvonly.connect()
91
+
92
+ time.sleep(5)
93
+
94
+ sendonly_stats = sendonly.get_stats()
95
+ recvonly_stats = recvonly.get_stats()
96
+
97
+ sendonly.disconnect()
98
+ recvonly.disconnect()
99
+
100
+ # codec が無かったら StopIteration 例外が上がる
101
+ sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
102
+ # 指定した video_codec_type が採用されているかどうか確認する
103
+ assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
104
+
105
+ # outbound-rtp が無かったら StopIteration 例外が上がる
106
+ outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
107
+ assert outbound_rtp_stats["encoderImplementation"] == "VideoToolbox"
108
+ assert outbound_rtp_stats["bytesSent"] > 0
109
+ assert outbound_rtp_stats["packetsSent"] > 0
110
+
111
+ # codec が無かったら StopIteration 例外が上がる
112
+ recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
113
+ # 指定した video_codec_type が採用されているかどうか確認する
114
+ assert recvonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
115
+
116
+ # outbound-rtp が無かったら StopIteration 例外が上がる
117
+ inbound_rtp_stats = next(s for s in recvonly_stats if s.get("type") == "inbound-rtp")
118
+ assert inbound_rtp_stats["decoderImplementation"] == "VideoToolbox"
119
+ assert inbound_rtp_stats["bytesReceived"] > 0
120
+ assert inbound_rtp_stats["packetsReceived"] > 0
121
+
122
+
123
+ @pytest.mark.skipif(
124
+ os.environ.get("APPLE_VIDEO_TOOLBOX") is None, reason="Apple Video Toolbox でのみ実行する"
125
+ )
61
126
  @pytest.mark.parametrize(
62
127
  (
63
128
  "video_codec_type",
@@ -89,7 +154,7 @@ def test_macos_video_hwa_sendonly(setup, video_codec_type):
89
154
  ("H265", "VideoToolbox", 200, 320, 180, 1),
90
155
  ],
91
156
  )
92
- def test_macos_simulcast(
157
+ def test_apple_video_toolbox_simulcast(
93
158
  setup,
94
159
  video_codec_type,
95
160
  expected_implementation,
@@ -186,115 +251,192 @@ def test_macos_simulcast(
186
251
  )
187
252
 
188
253
 
189
- @pytest.mark.skip(reason="ローカルでは成功する")
190
- def test_macos_h264_sendonly_recvonly(setup):
254
+ @pytest.mark.skipif(
255
+ os.environ.get("APPLE_VIDEO_TOOLBOX") is None, reason="Apple Video Toolbox でのみ実行する"
256
+ )
257
+ @pytest.mark.parametrize(
258
+ (
259
+ "video_codec_type",
260
+ "encoder_implementation",
261
+ "video_bit_rate",
262
+ "video_width",
263
+ "video_height",
264
+ ),
265
+ [
266
+ # どうやら scaleResolutionDownTo を指定すると規定されたテーブルのビットレートでは足りない模様
267
+ ("H264", "VideoToolbox", 1000, 320, 180),
268
+ ("H265", "VideoToolbox", 1000, 320, 180),
269
+ ],
270
+ )
271
+ def test_apple_video_toolbox_simulcast_authz_scale_resolution_to(
272
+ setup,
273
+ video_codec_type,
274
+ encoder_implementation,
275
+ video_bit_rate,
276
+ video_width,
277
+ video_height,
278
+ ):
191
279
  signaling_urls = setup.get("signaling_urls")
192
280
  channel_id_prefix = setup.get("channel_id_prefix")
193
- metadata = setup.get("metadata")
281
+ secret = setup.get("secret")
194
282
 
195
283
  channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"
196
284
 
285
+ simulcast_encodings = [
286
+ {
287
+ "rid": "r0",
288
+ "active": True,
289
+ "scaleResolutionDownTo": {"maxWidth": 320, "maxHeight": 180},
290
+ "scalabilityMode": "L1T1",
291
+ },
292
+ {
293
+ "rid": "r1",
294
+ "active": True,
295
+ "scaleResolutionDownTo": {"maxWidth": 320, "maxHeight": 180},
296
+ "scalabilityMode": "L1T1",
297
+ },
298
+ {
299
+ "rid": "r2",
300
+ "active": True,
301
+ "scaleResolutionDownTo": {"maxWidth": 320, "maxHeight": 180},
302
+ "scalabilityMode": "L1T1",
303
+ },
304
+ ]
305
+
306
+ access_token = jwt.encode(
307
+ {
308
+ "channel_id": channel_id,
309
+ "video": True,
310
+ "video_codec_type": video_codec_type,
311
+ "video_bit_rate": video_bit_rate,
312
+ "simulcast": True,
313
+ "simulcast_encodings": simulcast_encodings,
314
+ # 現在時刻 + 300 秒 (5分)
315
+ "exp": int(time.time()) + 300,
316
+ },
317
+ secret,
318
+ algorithm="HS256",
319
+ )
320
+
197
321
  sendonly = SoraClient(
198
322
  signaling_urls,
199
323
  SoraRole.SENDONLY,
200
324
  channel_id,
201
325
  audio=False,
202
326
  video=True,
203
- video_codec_type="H264",
204
- metadata=metadata,
205
- )
206
- sendonly.connect()
207
-
208
- recvonly = SoraClient(
209
- signaling_urls,
210
- SoraRole.RECVONLY,
211
- channel_id,
212
- metadata=metadata,
327
+ metadata={"access_token": access_token},
328
+ video_width=video_width,
329
+ video_height=video_height,
213
330
  )
214
- recvonly.connect()
331
+ sendonly.connect(fake_video=True)
215
332
 
216
333
  time.sleep(5)
217
334
 
218
- sendonly_stats = sendonly.get_stats()
219
- recvonly_stats = recvonly.get_stats()
335
+ # "type": "offer" の SDP で Simulcast があるかどうか
336
+ assert sendonly.offer_message is not None
337
+ assert sendonly.offer_message["sdp"] is not None
338
+ assert video_codec_type in sendonly.offer_message["sdp"]
339
+ assert "a=simulcast:recv r0;r1;r2" in sendonly.offer_message["sdp"]
220
340
 
221
- sendonly.disconnect()
222
- recvonly.disconnect()
341
+ assert "encodings" in sendonly.offer_message
342
+ assert len(sendonly.offer_message["encodings"]) == 3
223
343
 
224
- # codec が無かったら StopIteration 例外が上がる
225
- sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
226
- # H.264 が採用されているかどうか確認する
227
- assert sendonly_codec_stats["mimeType"] == "video/H264"
344
+ assert sendonly.offer_message["encodings"][0]["rid"] == simulcast_encodings[0]["rid"]
345
+ assert sendonly.offer_message["encodings"][1]["rid"] == simulcast_encodings[1]["rid"]
346
+ assert sendonly.offer_message["encodings"][2]["rid"] == simulcast_encodings[2]["rid"]
228
347
 
229
- # outbound-rtp が無かったら StopIteration 例外が上がる
230
- outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
231
- assert outbound_rtp_stats["encoderImplementation"] == "VideoToolbox"
232
- assert outbound_rtp_stats["bytesSent"] > 0
233
- assert outbound_rtp_stats["packetsSent"] > 0
348
+ assert sendonly.offer_message["encodings"][0]["active"] == simulcast_encodings[0]["active"]
349
+ assert sendonly.offer_message["encodings"][1]["active"] == simulcast_encodings[1]["active"]
350
+ assert sendonly.offer_message["encodings"][2]["active"] == simulcast_encodings[2]["active"]
234
351
 
235
- # codec が無かったら StopIteration 例外が上がる
236
- recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
237
- # H.264 が採用されているかどうか確認する
238
- assert recvonly_codec_stats["mimeType"] == "video/H264"
239
-
240
- # outbound-rtp が無かったら StopIteration 例外が上がる
241
- inbound_rtp_stats = next(s for s in recvonly_stats if s.get("type") == "inbound-rtp")
242
- assert inbound_rtp_stats["decoderImplementation"] == "VideoToolbox"
243
- assert inbound_rtp_stats["bytesReceived"] > 0
244
- assert inbound_rtp_stats["packetsReceived"] > 0
245
-
246
-
247
- @pytest.mark.skip(reason="ローカルでは成功する")
248
- def test_macos_h265_sendonly_recvonly(setup):
249
- signaling_urls = setup.get("signaling_urls")
250
- channel_id_prefix = setup.get("channel_id_prefix")
251
- metadata = setup.get("metadata")
352
+ assert (
353
+ sendonly.offer_message["encodings"][0]["scaleResolutionDownTo"]["maxWidth"]
354
+ == simulcast_encodings[0]["scaleResolutionDownTo"]["maxWidth"]
355
+ )
356
+ assert (
357
+ sendonly.offer_message["encodings"][1]["scaleResolutionDownTo"]["maxWidth"]
358
+ == simulcast_encodings[1]["scaleResolutionDownTo"]["maxWidth"]
359
+ )
360
+ assert (
361
+ sendonly.offer_message["encodings"][2]["scaleResolutionDownTo"]["maxWidth"]
362
+ == simulcast_encodings[2]["scaleResolutionDownTo"]["maxWidth"]
363
+ )
252
364
 
253
- channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"
365
+ assert (
366
+ sendonly.offer_message["encodings"][0]["scalabilityMode"]
367
+ == simulcast_encodings[0]["scalabilityMode"]
368
+ )
254
369
 
255
- sendonly = SoraClient(
256
- signaling_urls,
257
- SoraRole.SENDONLY,
258
- channel_id,
259
- audio=False,
260
- video=True,
261
- video_codec_type="H265",
262
- metadata=metadata,
370
+ assert (
371
+ sendonly.offer_message["encodings"][1]["scalabilityMode"]
372
+ == simulcast_encodings[1]["scalabilityMode"]
263
373
  )
264
- sendonly.connect()
265
374
 
266
- recvonly = SoraClient(
267
- signaling_urls,
268
- SoraRole.RECVONLY,
269
- channel_id,
270
- metadata=metadata,
375
+ assert (
376
+ sendonly.offer_message["encodings"][2]["scalabilityMode"]
377
+ == simulcast_encodings[2]["scalabilityMode"]
271
378
  )
272
- recvonly.connect()
273
379
 
274
- time.sleep(5)
380
+ # "type": "answer" の SDP で Simulcast があるかどうか
381
+ assert sendonly.answer_message is not None
382
+ assert "sdp" in sendonly.answer_message
383
+ assert "a=simulcast:send r0;r1;r2" in sendonly.answer_message["sdp"]
275
384
 
276
385
  sendonly_stats = sendonly.get_stats()
277
- recvonly_stats = recvonly.get_stats()
278
-
279
386
  sendonly.disconnect()
280
- recvonly.disconnect()
281
387
 
282
388
  # codec が無かったら StopIteration 例外が上がる
283
389
  sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
284
- assert sendonly_codec_stats["mimeType"] == "video/H265"
390
+ assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
285
391
 
286
- # outbound-rtp が無かったら StopIteration 例外が上がる
287
- outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
288
- assert outbound_rtp_stats["encoderImplementation"] == "VideoToolbox"
289
- assert outbound_rtp_stats["bytesSent"] > 0
290
- assert outbound_rtp_stats["packetsSent"] > 0
392
+ # 複数の outbound-rtp 統計情報を取得
393
+ outbound_rtp_stats = [
394
+ s for s in sendonly_stats if s.get("type") == "outbound-rtp" and s.get("kind") == "video"
395
+ ]
396
+ # simulcast_count に関係なく統計情報はかならず 3 本出力される
397
+ # これは SDP で rid で ~r0 とかやる減るはず
398
+ assert len(outbound_rtp_stats) == 3
291
399
 
292
- # codec が無かったら StopIteration 例外が上がる
293
- recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
294
- assert recvonly_codec_stats["mimeType"] == "video/H265"
400
+ # rid でソート
401
+ sorted_stats = sorted(outbound_rtp_stats, key=lambda x: x.get("rid", ""))
295
402
 
296
- # outbound-rtp が無かったら StopIteration 例外が上がる
297
- inbound_rtp_stats = next(s for s in recvonly_stats if s.get("type") == "inbound-rtp")
298
- assert inbound_rtp_stats["decoderImplementation"] == "VideoToolbox"
299
- assert inbound_rtp_stats["bytesReceived"] > 0
300
- assert inbound_rtp_stats["packetsReceived"] > 0
403
+ for i, s in enumerate(sorted_stats):
404
+ assert s["rid"] == f"r{i}"
405
+ assert s["kind"] == "video"
406
+
407
+ # VP8 の場合は scaleResolutionDownTo を指定すると SimulcastEncoderAdapter が無くなる
408
+ # TODO: 念のため他の挙動も確認すること
409
+ if video_codec_type == "VP9":
410
+ assert "SimulcastEncoderAdapter" in s["encoderImplementation"]
411
+ assert encoder_implementation in s["encoderImplementation"]
412
+
413
+ assert s["keyFramesEncoded"] > 0
414
+ assert s["bytesSent"] > 500
415
+ assert s["packetsSent"] > 10
416
+
417
+ assert s["frameWidth"] == 320
418
+ assert s["frameHeight"] == 176
419
+
420
+ # FIXME:これは libwebrtc 側の挙動を制御できず L1T2 になってしまう
421
+ scalability_mode = None
422
+ # FIXME: scalabilityMode がない場合がある
423
+ if "scalabilityMode" in s:
424
+ scalability_mode = s["scalabilityMode"]
425
+ assert s["scalabilityMode"] == "L1T2"
426
+
427
+ # targetBitrate が指定したビットレートの 90% 以上、100% 以下に収まることを確認
428
+ expected_bitrate = video_bit_rate * 1000
429
+ print(
430
+ s["rid"],
431
+ video_codec_type,
432
+ s["encoderImplementation"],
433
+ scalability_mode,
434
+ expected_bitrate,
435
+ s["targetBitrate"],
436
+ s["frameWidth"],
437
+ s["frameHeight"],
438
+ s["bytesSent"],
439
+ s["packetsSent"],
440
+ )
441
+ # 期待値の 20% 以上、100% 以下に収まることを確認
442
+ assert expected_bitrate * 0.2 <= s["targetBitrate"] <= expected_bitrate
@@ -7,6 +7,7 @@ import pytest
7
7
  from client import SoraClient, SoraRole
8
8
 
9
9
 
10
+ @pytest.mark.skipif(sys.platform == "darwin", reason="Apple では SW コーデックは動作させない")
10
11
  @pytest.mark.parametrize(
11
12
  (
12
13
  "video_codec_type",
@@ -91,7 +92,6 @@ def test_simulcast_authz_scale_resolution_to(
91
92
  assert sendonly.offer_message["sdp"] is not None
92
93
  assert video_codec_type in sendonly.offer_message["sdp"]
93
94
  assert "a=simulcast:recv r0;r1;r2" in sendonly.offer_message["sdp"]
94
- sendonly_stats = sendonly.get_stats()
95
95
 
96
96
  assert "encodings" in sendonly.offer_message
97
97
  assert len(sendonly.offer_message["encodings"]) == 3
@@ -121,22 +121,25 @@ def test_simulcast_authz_scale_resolution_to(
121
121
  sendonly.offer_message["encodings"][0]["scalabilityMode"]
122
122
  == simulcast_encodings[0]["scalabilityMode"]
123
123
  )
124
+
124
125
  assert (
125
126
  sendonly.offer_message["encodings"][1]["scalabilityMode"]
126
127
  == simulcast_encodings[1]["scalabilityMode"]
127
128
  )
129
+
128
130
  assert (
129
131
  sendonly.offer_message["encodings"][2]["scalabilityMode"]
130
132
  == simulcast_encodings[2]["scalabilityMode"]
131
133
  )
132
134
 
133
- sendonly.disconnect()
134
-
135
135
  # "type": "answer" の SDP で Simulcast があるかどうか
136
136
  assert sendonly.answer_message is not None
137
137
  assert "sdp" in sendonly.answer_message
138
138
  assert "a=simulcast:send r0;r1;r2" in sendonly.answer_message["sdp"]
139
139
 
140
+ sendonly_stats = sendonly.get_stats()
141
+ sendonly.disconnect()
142
+
140
143
  # codec が無かったら StopIteration 例外が上がる
141
144
  sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
142
145
  assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
@@ -170,7 +173,11 @@ def test_simulcast_authz_scale_resolution_to(
170
173
  assert s["frameHeight"] == 352
171
174
 
172
175
  # FIXME:これは libwebrtc 側の挙動を制御できず L1T2 になってしまう
173
- assert s["scalabilityMode"] == "L1T2"
176
+ scalability_mode = None
177
+ # FIXME: scalabilityMode がない場合がある
178
+ if "scalabilityMode" in s:
179
+ scalability_mode = s["scalabilityMode"]
180
+ assert s["scalabilityMode"] == "L1T2"
174
181
 
175
182
  # targetBitrate が指定したビットレートの 90% 以上、100% 以下に収まることを確認
176
183
  expected_bitrate = video_bit_rate * 1000
@@ -178,7 +185,7 @@ def test_simulcast_authz_scale_resolution_to(
178
185
  s["rid"],
179
186
  video_codec_type,
180
187
  s["encoderImplementation"],
181
- s["scalabilityMode"],
188
+ scalability_mode,
182
189
  expected_bitrate,
183
190
  s["targetBitrate"],
184
191
  s["frameWidth"],
@@ -4,7 +4,12 @@ import time
4
4
  import uuid
5
5
 
6
6
  import pytest
7
- from client import SoraClient, SoraRole
7
+ from client import (
8
+ SoraClient,
9
+ SoraRole,
10
+ codec_type_string_to_codec_type,
11
+ is_codec_supported,
12
+ )
8
13
 
9
14
  from sora_sdk import (
10
15
  SoraVideoCodecImplementation,
@@ -41,8 +46,10 @@ def test_intel_vpl_available(setup):
41
46
  # Sora Python SDK では VPL VP9 Encoder が正常に動作しないため無効
42
47
  assert c.encoder is False
43
48
  case SoraVideoCodecType.AV1:
44
- assert c.decoder is True
45
- assert c.encoder is True
49
+ # チップによって対応指定ないものがあるので判断しない
50
+ # assert c.decoder is True
51
+ # assert c.encoder is True
52
+ pass
46
53
  case SoraVideoCodecType.H264:
47
54
  assert c.decoder is True
48
55
  assert c.encoder is True
@@ -76,6 +83,9 @@ def test_intel_vpl_sendonly(
76
83
  expected_codec_implementation,
77
84
  preference_codec_implementation,
78
85
  ):
86
+ if not is_codec_supported(video_codec_type, SoraVideoCodecImplementation.INTEL_VPL):
87
+ pytest.skip(f"このチップでは {video_codec_type} がサポートされていません")
88
+
79
89
  signaling_urls = setup.get("signaling_urls")
80
90
  channel_id_prefix = setup.get("channel_id_prefix")
81
91
  metadata = setup.get("metadata")
@@ -184,6 +194,9 @@ def test_intel_vpl_simulcast(
184
194
  video_height,
185
195
  simulcast_count,
186
196
  ):
197
+ if not is_codec_supported(video_codec_type, SoraVideoCodecImplementation.INTEL_VPL):
198
+ pytest.skip(f"このチップでは {video_codec_type} がサポートされていません")
199
+
187
200
  signaling_urls = setup.get("signaling_urls")
188
201
  channel_id_prefix = setup.get("channel_id_prefix")
189
202
  metadata = setup.get("metadata")
@@ -205,15 +218,7 @@ def test_intel_vpl_simulcast(
205
218
  video_codec_preference=SoraVideoCodecPreference(
206
219
  codecs=[
207
220
  SoraVideoCodecPreference.Codec(
208
- type=SoraVideoCodecType.AV1,
209
- encoder=SoraVideoCodecImplementation.INTEL_VPL,
210
- ),
211
- SoraVideoCodecPreference.Codec(
212
- type=SoraVideoCodecType.H264,
213
- encoder=SoraVideoCodecImplementation.INTEL_VPL,
214
- ),
215
- SoraVideoCodecPreference.Codec(
216
- type=SoraVideoCodecType.H265,
221
+ type=codec_type_string_to_codec_type(video_codec_type),
217
222
  encoder=SoraVideoCodecImplementation.INTEL_VPL,
218
223
  ),
219
224
  ]
@@ -315,6 +320,9 @@ def test_intel_vpl_sendonly_recvonly(
315
320
  expected_codec_implementation,
316
321
  preference_codec_implementation,
317
322
  ):
323
+ if not is_codec_supported(video_codec_type, SoraVideoCodecImplementation.INTEL_VPL):
324
+ pytest.skip(f"このチップでは {video_codec_type} がサポートされていません")
325
+
318
326
  signaling_urls = setup.get("signaling_urls")
319
327
  channel_id_prefix = setup.get("channel_id_prefix")
320
328
  metadata = setup.get("metadata")
@@ -579,6 +587,9 @@ def test_intel_vpl_vp9_sendonly_recvonly(setup):
579
587
  def test_intel_vpl_av1_mini_resolution(
580
588
  setup, video_codec_type, expected_implementation, video_bit_rate, video_width, video_height
581
589
  ):
590
+ if not is_codec_supported(video_codec_type, SoraVideoCodecImplementation.INTEL_VPL):
591
+ pytest.skip(f"このチップでは {video_codec_type} がサポートされていません")
592
+
582
593
  signaling_urls = setup.get("signaling_urls")
583
594
  channel_id_prefix = setup.get("channel_id_prefix")
584
595
  metadata = setup.get("metadata")
@@ -689,6 +700,10 @@ def test_intel_vpl_sendonly_recvonly_sw_hw(
689
700
  - 送信はソフトウェアだけど、受信はハードウェアでやる
690
701
  - 送信はハードウェアだけど、受信はソフトウェアでやる
691
702
  """
703
+
704
+ if not is_codec_supported(video_codec_type, SoraVideoCodecImplementation.INTEL_VPL):
705
+ pytest.skip(f"このチップでは {video_codec_type} がサポートされていません")
706
+
692
707
  signaling_urls = setup.get("signaling_urls")
693
708
  channel_id_prefix = setup.get("channel_id_prefix")
694
709
  metadata = setup.get("metadata")
@@ -4,9 +4,18 @@ import time
4
4
  import uuid
5
5
 
6
6
  import pytest
7
- from client import SoraClient, SoraRole
7
+ from client import (
8
+ SoraClient,
9
+ SoraRole,
10
+ codec_type_string_to_codec_type,
11
+ is_codec_supported,
12
+ )
8
13
 
9
- from sora_sdk import SoraVideoCodecImplementation, SoraVideoCodecPreference, SoraVideoCodecType
14
+ from sora_sdk import (
15
+ SoraVideoCodecImplementation,
16
+ SoraVideoCodecPreference,
17
+ SoraVideoCodecType,
18
+ )
10
19
 
11
20
 
12
21
  # @pytest.mark.skip()
@@ -19,13 +28,17 @@ from sora_sdk import SoraVideoCodecImplementation, SoraVideoCodecPreference, Sor
19
28
  "expected_implementation",
20
29
  ),
21
30
  [
22
- ("VP9", "NvCodec"),
23
31
  ("AV1", "NvCodec"),
24
32
  ("H264", "NvCodec"),
25
33
  ("H265", "NvCodec"),
26
34
  ],
27
35
  )
28
- def test_intel_vpl_sendonly(setup, video_codec_type, expected_implementation):
36
+ def test_nvidia_codec_sdk_sendonly(setup, video_codec_type, expected_implementation):
37
+ if not is_codec_supported(
38
+ video_codec_type, SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK
39
+ ):
40
+ pytest.skip(f"このチップでは {video_codec_type} がサポートされていません")
41
+
29
42
  signaling_urls = setup.get("signaling_urls")
30
43
  channel_id_prefix = setup.get("channel_id_prefix")
31
44
  metadata = setup.get("metadata")
@@ -43,15 +56,7 @@ def test_intel_vpl_sendonly(setup, video_codec_type, expected_implementation):
43
56
  video_codec_preference=SoraVideoCodecPreference(
44
57
  codecs=[
45
58
  SoraVideoCodecPreference.Codec(
46
- type=SoraVideoCodecType.AV1,
47
- encoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
48
- ),
49
- SoraVideoCodecPreference.Codec(
50
- type=SoraVideoCodecType.H264,
51
- encoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
52
- ),
53
- SoraVideoCodecPreference.Codec(
54
- type=SoraVideoCodecType.H265,
59
+ type=codec_type_string_to_codec_type(video_codec_type),
55
60
  encoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
56
61
  ),
57
62
  ]
@@ -108,34 +113,32 @@ def test_intel_vpl_sendonly(setup, video_codec_type, expected_implementation):
108
113
  # FIXME: AV1 では、解像度が一定数より低くなる場合、エラーになるのでコメントアウトしている
109
114
  [
110
115
  # 1080p
111
- ("VP9", "NvCodec", 5000, 1920, 1080, 3),
116
+ ("AV1", "NvCodec", 5000, 1920, 1080, 3),
112
117
  ("H264", "NvCodec", 5000, 1920, 1080, 3),
113
118
  ("H265", "NvCodec", 5000, 1920, 1080, 3),
114
119
  # 720p
115
- ("VP9", "NvCodec", 2500, 1280, 720, 3),
120
+ ("AV1", "NvCodec", 2500, 1280, 720, 3),
116
121
  ("H264", "NvCodec", 2500, 1280, 720, 3),
117
122
  ("H265", "NvCodec", 2500, 1280, 720, 3),
118
123
  # 540p
119
- ("VP9", "NvCodec", 1200, 960, 540, 3),
124
+ ("AV1", "NvCodec", 1200, 960, 540, 3),
120
125
  ("H264", "NvCodec", 1200, 960, 540, 3),
121
126
  ("H265", "NvCodec", 1200, 960, 540, 3),
122
127
  # 360p
123
- ("VP9", "NvCodec", 700, 640, 360, 2),
128
+ ("AV1", "NvCodec", 700, 640, 360, 2),
124
129
  ("H264", "NvCodec", 700, 640, 360, 2),
125
130
  ("H265", "NvCodec", 700, 640, 360, 2),
126
131
  # 270p
127
- ("VP9", "NvCodec", 450, 480, 270, 2),
128
- ("H264", "NvCodec", 450, 480, 270, 2),
129
- ("H265", "NvCodec", 450, 480, 270, 2),
132
+ # ("H264", "NvCodec", 450, 480, 270, 2),
133
+ # ("H265", "NvCodec", 450, 480, 270, 2),
130
134
  # 180p
131
- ("VP9", "NvCodec", 200, 320, 180, 1),
132
- ("H264", "NvCodec", 200, 320, 180, 1),
133
- ("H265", "NvCodec", 142, 320, 180, 1),
135
+ # ("H264", "NvCodec", 200, 320, 180, 1),
136
+ # ("H265", "NvCodec", 142, 320, 180, 1),
134
137
  # 135p
135
- ("H265", "NvCodec", 101, 240, 135, 1),
138
+ # ("H265", "NvCodec", 101, 240, 135, 1),
136
139
  ],
137
140
  )
138
- def test_intel_vpl_simulcast(
141
+ def test_nvidia_codec_sdk_simulcast(
139
142
  setup,
140
143
  video_codec_type,
141
144
  expected_implementation,
@@ -144,6 +147,11 @@ def test_intel_vpl_simulcast(
144
147
  video_height,
145
148
  simulcast_count,
146
149
  ):
150
+ if not is_codec_supported(
151
+ video_codec_type, SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK
152
+ ):
153
+ pytest.skip(f"このチップでは {video_codec_type} がサポートされていません")
154
+
147
155
  signaling_urls = setup.get("signaling_urls")
148
156
  channel_id_prefix = setup.get("channel_id_prefix")
149
157
  metadata = setup.get("metadata")
@@ -165,15 +173,7 @@ def test_intel_vpl_simulcast(
165
173
  video_codec_preference=SoraVideoCodecPreference(
166
174
  codecs=[
167
175
  SoraVideoCodecPreference.Codec(
168
- type=SoraVideoCodecType.AV1,
169
- encoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
170
- ),
171
- SoraVideoCodecPreference.Codec(
172
- type=SoraVideoCodecType.H264,
173
- encoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
174
- ),
175
- SoraVideoCodecPreference.Codec(
176
- type=SoraVideoCodecType.H265,
176
+ type=codec_type_string_to_codec_type(video_codec_type),
177
177
  encoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
178
178
  ),
179
179
  ]
@@ -258,13 +258,17 @@ def test_intel_vpl_simulcast(
258
258
  "expected_implementation",
259
259
  ),
260
260
  [
261
- ("VP9", "NvCodec"),
262
261
  ("AV1", "NvCodec"),
263
262
  ("H264", "NvCodec"),
264
263
  ("H265", "NvCodec"),
265
264
  ],
266
265
  )
267
- def test_intel_vpl_sendonly_recvonly(setup, video_codec_type, expected_implementation):
266
+ def test_nvidia_codec_sdk_sendonly_recvonly(setup, video_codec_type, expected_implementation):
267
+ if not is_codec_supported(
268
+ video_codec_type, SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK
269
+ ):
270
+ pytest.skip(f"このチップでは {video_codec_type} がサポートされていません")
271
+
268
272
  signaling_urls = setup.get("signaling_urls")
269
273
  channel_id_prefix = setup.get("channel_id_prefix")
270
274
  metadata = setup.get("metadata")
@@ -282,15 +286,7 @@ def test_intel_vpl_sendonly_recvonly(setup, video_codec_type, expected_implement
282
286
  video_codec_preference=SoraVideoCodecPreference(
283
287
  codecs=[
284
288
  SoraVideoCodecPreference.Codec(
285
- type=SoraVideoCodecType.AV1,
286
- encoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
287
- ),
288
- SoraVideoCodecPreference.Codec(
289
- type=SoraVideoCodecType.H264,
290
- encoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
291
- ),
292
- SoraVideoCodecPreference.Codec(
293
- type=SoraVideoCodecType.H265,
289
+ type=codec_type_string_to_codec_type(video_codec_type),
294
290
  encoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
295
291
  ),
296
292
  ]
@@ -306,15 +302,7 @@ def test_intel_vpl_sendonly_recvonly(setup, video_codec_type, expected_implement
306
302
  video_codec_preference=SoraVideoCodecPreference(
307
303
  codecs=[
308
304
  SoraVideoCodecPreference.Codec(
309
- type=SoraVideoCodecType.AV1,
310
- decoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
311
- ),
312
- SoraVideoCodecPreference.Codec(
313
- type=SoraVideoCodecType.H264,
314
- decoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
315
- ),
316
- SoraVideoCodecPreference.Codec(
317
- type=SoraVideoCodecType.H265,
305
+ type=codec_type_string_to_codec_type(video_codec_type),
318
306
  decoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
319
307
  ),
320
308
  ]
@@ -361,3 +349,94 @@ def test_intel_vpl_sendonly_recvonly(setup, video_codec_type, expected_implement
361
349
  assert inbound_rtp_stats["decoderImplementation"] == expected_implementation
362
350
  assert inbound_rtp_stats["bytesReceived"] > 0
363
351
  assert inbound_rtp_stats["packetsReceived"] > 0
352
+
353
+
354
+ @pytest.mark.skipif(
355
+ os.environ.get("NVIDIA_VIDEO_SDK") is None, reason="NVIDIA Video Codec SDK でのみ実行する"
356
+ )
357
+ def test_nvidia_codec_sdk_vp9_hwa_decoder(setup):
358
+ """
359
+ NVIDIA Video Codec SDK VP9 はデコーダーは利用できるので、そのテスト
360
+ """
361
+ signaling_urls = setup.get("signaling_urls")
362
+ channel_id_prefix = setup.get("channel_id_prefix")
363
+ metadata = setup.get("metadata")
364
+
365
+ channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"
366
+
367
+ sendonly = SoraClient(
368
+ signaling_urls,
369
+ SoraRole.SENDONLY,
370
+ channel_id,
371
+ audio=False,
372
+ video=True,
373
+ video_codec_type="VP9",
374
+ metadata=metadata,
375
+ video_codec_preference=SoraVideoCodecPreference(
376
+ codecs=[
377
+ SoraVideoCodecPreference.Codec(
378
+ type=SoraVideoCodecType.VP9,
379
+ # NVIDIA Video Codec SDK で VP9 Encoder は搭載していないので INTERNAL を指定
380
+ encoder=SoraVideoCodecImplementation.INTERNAL,
381
+ ),
382
+ ]
383
+ ),
384
+ )
385
+ sendonly.connect(fake_video=True)
386
+
387
+ recvonly = SoraClient(
388
+ signaling_urls,
389
+ SoraRole.RECVONLY,
390
+ channel_id,
391
+ metadata=metadata,
392
+ video_codec_preference=SoraVideoCodecPreference(
393
+ codecs=[
394
+ SoraVideoCodecPreference.Codec(
395
+ type=SoraVideoCodecType.VP9,
396
+ # NVIDIA Video Codec SDK で VP9 Decoder は搭載しているので NVIDIA_VIDEO_CODEC_SDK を指定
397
+ decoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
398
+ ),
399
+ ]
400
+ ),
401
+ )
402
+ recvonly.connect()
403
+
404
+ time.sleep(5)
405
+
406
+ sendonly_stats = sendonly.get_stats()
407
+ recvonly_stats = recvonly.get_stats()
408
+
409
+ sendonly.disconnect()
410
+ recvonly.disconnect()
411
+
412
+ # offer の sdp に video_codec_type が含まれているかどうかを確認している
413
+ assert sendonly.offer_message is not None
414
+ assert "sdp" in sendonly.offer_message
415
+ assert "VP9" in sendonly.offer_message["sdp"]
416
+
417
+ # answer の sdp に video_codec_type が含まれているかどうかを確認している
418
+ assert sendonly.answer_message is not None
419
+ assert "sdp" in sendonly.answer_message
420
+ assert "VP9" in sendonly.answer_message["sdp"]
421
+
422
+ # codec が無かったら StopIteration 例外が上がる
423
+ sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
424
+ # VP9 が採用されているかどうか確認する
425
+ assert sendonly_codec_stats["mimeType"] == "video/VP9"
426
+
427
+ # outbound-rtp が無かったら StopIteration 例外が上がる
428
+ outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
429
+ assert outbound_rtp_stats["encoderImplementation"] == "libvpx"
430
+ assert outbound_rtp_stats["bytesSent"] > 0
431
+ assert outbound_rtp_stats["packetsSent"] > 0
432
+
433
+ # codec が無かったら StopIteration 例外が上がる
434
+ recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
435
+ # VP9 が採用されているかどうか確認する
436
+ assert recvonly_codec_stats["mimeType"] == "video/VP9"
437
+
438
+ # inbound-rtp が無かったら StopIteration 例外が上がる
439
+ inbound_rtp_stats = next(s for s in recvonly_stats if s.get("type") == "inbound-rtp")
440
+ assert inbound_rtp_stats["decoderImplementation"] == "NvCodec"
441
+ assert inbound_rtp_stats["bytesReceived"] > 0
442
+ assert inbound_rtp_stats["packetsReceived"] > 0