sora-sdk 2025.1.0.dev0__tar.gz → 2025.1.0.dev4__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 (39) hide show
  1. {sora_sdk-2025.1.0.dev0/src/sora_sdk.egg-info → sora_sdk-2025.1.0.dev4}/PKG-INFO +9 -8
  2. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/README.md +6 -6
  3. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/buildbase.py +1 -1
  4. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/pyproject.toml +5 -4
  5. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/src/sora_sdk/sora_sdk_ext.cpython-310-darwin.so +0 -0
  6. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/src/sora_sdk/sora_sdk_ext.pyi +10 -1
  7. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4/src/sora_sdk.egg-info}/PKG-INFO +9 -8
  8. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/src/sora_sdk.egg-info/SOURCES.txt +3 -0
  9. sora_sdk-2025.1.0.dev4/tests/test_authz.py +125 -0
  10. sora_sdk-2025.1.0.dev4/tests/test_authz_simulcast.py +151 -0
  11. sora_sdk-2025.1.0.dev4/tests/test_degradation_preference.py +240 -0
  12. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_macos.py +81 -14
  13. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_openh264.py +75 -13
  14. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_sendonly_recvonly.py +4 -7
  15. sora_sdk-2025.1.0.dev4/tests/test_simulcast.py +147 -0
  16. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_sora_disconnect.py +125 -6
  17. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_type_disconnect.py +1 -1
  18. sora_sdk-2025.1.0.dev0/tests/test_simulcast.py +0 -68
  19. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/LICENSE +0 -0
  20. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/MANIFEST.in +0 -0
  21. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/NOTICE.md +0 -0
  22. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/pypath.py +0 -0
  23. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/run.py +0 -0
  24. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/setup.cfg +0 -0
  25. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/setup.py +0 -0
  26. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/src/sora_sdk/__init__.py +0 -0
  27. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/src/sora_sdk/py.typed +0 -0
  28. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/src/sora_sdk.egg-info/dependency_links.txt +0 -0
  29. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/src/sora_sdk.egg-info/top_level.txt +0 -0
  30. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_ca_cert.py +0 -0
  31. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_encoded_transform.py +0 -0
  32. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_messaging.py +0 -0
  33. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_messaging_header.py +0 -0
  34. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_re_offer_re_answer_sdp.py +0 -0
  35. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_signaling.py +0 -0
  36. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_signaling_message.py +0 -0
  37. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_signaling_notify.py +0 -0
  38. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_type_switched.py +0 -0
  39. {sora_sdk-2025.1.0.dev0 → sora_sdk-2025.1.0.dev4}/tests/test_vad.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: sora_sdk
3
- Version: 2025.1.0.dev0
3
+ Version: 2025.1.0.dev4
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>
@@ -195,6 +195,7 @@ Requires-Python: >=3.10
195
195
  Description-Content-Type: text/markdown
196
196
  License-File: LICENSE
197
197
  License-File: NOTICE.md
198
+ Dynamic: home-page
198
199
 
199
200
  # Sora Python SDK
200
201
 
@@ -228,6 +229,8 @@ Please read <https://github.com/shiguredo/oss/blob/master/README.en.md> before u
228
229
  - Windows / macOS / Linux (Ubuntu) プラットフォームに対応
229
230
  - [WebRTC 統計情報](https://www.w3.org/TR/webrtc-stats/) の取得が可能
230
231
  - [WebRTC Encoded Transform](https://www.w3.org/TR/webrtc-encoded-transform/) に対応
232
+ - 回線が不安定になった際、解像度とフレームレートどちらを維持するかの設定をする [DegradationPreference](https://w3c.github.io/mst-content-hint/#degradation-preference-when-encoding) に対応
233
+ - MAINTAIN_FRAMERATE / MAINTAIN_RESOLUTION / BALANCED が指定できる
231
234
  - Intel / Apple / NVIDIA のハードウェアデコーダー/エンコーダーに対応
232
235
  - Intel VPL (AV1 / H.264 / H.265)
233
236
  - Apple Video Toolbox (H.264 / H.265)
@@ -282,7 +285,7 @@ PyPI 経由ではインストールできません。
282
285
 
283
286
  ## システム条件
284
287
 
285
- - WebRTC SFU Sora 2023.2.0 以降
288
+ - WebRTC SFU Sora 2024.1.0 以降
286
289
  - Python 3.10 以上
287
290
 
288
291
  ## 対応プラットフォーム
@@ -319,12 +322,10 @@ PyPI 経由ではインストールできません。
319
322
  ### 優先実装が可能な機能一覧
320
323
 
321
324
  - Windows 11 arm64
322
- - macOS Sonoma 13 arm64
323
325
  - Ubuntu 22.04 arm64
324
326
  - Ubuntu 20.04 arm64 (NVIDIA Jetson JetPack SDK 5)
325
327
  - AMD Video Core Next (VCN) 対応
326
328
  - VP9 / AV1 / H.264 / H.265
327
- - Python 3.9 以前への対応
328
329
 
329
330
  ## サポートについて
330
331
 
@@ -347,9 +348,9 @@ Discord へお願いします。
347
348
  Apache License 2.0
348
349
 
349
350
  ```text
350
- Copyright 2023-2024, tnoho (Original Author)
351
- Copyright 2023-2024, Wandbox LLC (Original Author)
352
- Copyright 2023-2024, Shiguredo Inc.
351
+ Copyright 2023-2025, tnoho (Original Author)
352
+ Copyright 2023-2025, Wandbox LLC (Original Author)
353
+ Copyright 2023-2025, Shiguredo Inc.
353
354
 
354
355
  Licensed under the Apache License, Version 2.0 (the "License");
355
356
  you may not use this file except in compliance with the License.
@@ -30,6 +30,8 @@ Please read <https://github.com/shiguredo/oss/blob/master/README.en.md> before u
30
30
  - Windows / macOS / Linux (Ubuntu) プラットフォームに対応
31
31
  - [WebRTC 統計情報](https://www.w3.org/TR/webrtc-stats/) の取得が可能
32
32
  - [WebRTC Encoded Transform](https://www.w3.org/TR/webrtc-encoded-transform/) に対応
33
+ - 回線が不安定になった際、解像度とフレームレートどちらを維持するかの設定をする [DegradationPreference](https://w3c.github.io/mst-content-hint/#degradation-preference-when-encoding) に対応
34
+ - MAINTAIN_FRAMERATE / MAINTAIN_RESOLUTION / BALANCED が指定できる
33
35
  - Intel / Apple / NVIDIA のハードウェアデコーダー/エンコーダーに対応
34
36
  - Intel VPL (AV1 / H.264 / H.265)
35
37
  - Apple Video Toolbox (H.264 / H.265)
@@ -84,7 +86,7 @@ PyPI 経由ではインストールできません。
84
86
 
85
87
  ## システム条件
86
88
 
87
- - WebRTC SFU Sora 2023.2.0 以降
89
+ - WebRTC SFU Sora 2024.1.0 以降
88
90
  - Python 3.10 以上
89
91
 
90
92
  ## 対応プラットフォーム
@@ -121,12 +123,10 @@ PyPI 経由ではインストールできません。
121
123
  ### 優先実装が可能な機能一覧
122
124
 
123
125
  - Windows 11 arm64
124
- - macOS Sonoma 13 arm64
125
126
  - Ubuntu 22.04 arm64
126
127
  - Ubuntu 20.04 arm64 (NVIDIA Jetson JetPack SDK 5)
127
128
  - AMD Video Core Next (VCN) 対応
128
129
  - VP9 / AV1 / H.264 / H.265
129
- - Python 3.9 以前への対応
130
130
 
131
131
  ## サポートについて
132
132
 
@@ -149,9 +149,9 @@ Discord へお願いします。
149
149
  Apache License 2.0
150
150
 
151
151
  ```text
152
- Copyright 2023-2024, tnoho (Original Author)
153
- Copyright 2023-2024, Wandbox LLC (Original Author)
154
- Copyright 2023-2024, Shiguredo Inc.
152
+ Copyright 2023-2025, tnoho (Original Author)
153
+ Copyright 2023-2025, Wandbox LLC (Original Author)
154
+ Copyright 2023-2025, Shiguredo Inc.
155
155
 
156
156
  Licensed under the Apache License, Version 2.0 (the "License");
157
157
  you may not use this file except in compliance with the License.
@@ -691,7 +691,7 @@ def build_and_install_boost(
691
691
  ):
692
692
  version_underscore = version.replace(".", "_")
693
693
  archive = download(
694
- f"https://boostorg.jfrog.io/artifactory/main/release/{version}/source/boost_{version_underscore}.tar.gz",
694
+ f"https://archives.boost.io/release/{version}/source/boost_{version_underscore}.tar.gz",
695
695
  source_dir,
696
696
  )
697
697
  extract(archive, output_dir=build_dir, output_dirname="boost")
@@ -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.dev0"
4
+ version = "2025.1.0.dev4"
5
5
  description = "WebRTC SFU Sora Python SDK"
6
6
  readme = "README.md"
7
7
  license = { file = "LICENSE" }
@@ -51,23 +51,24 @@ Discord = "https://discord.gg/shiguredo"
51
51
  # - https://setuptools.pypa.io/en/latest/build_meta.html
52
52
  # - setuptools の build-system サポートについて解説されていて参考になる
53
53
  [build-system]
54
- requires = ["setuptools>=75.6", "wheel~=0.45.1"]
54
+ requires = ["setuptools~=75.7", "wheel~=0.45.1"]
55
55
  build-backend = "setuptools.build_meta"
56
56
 
57
57
  [tool.uv]
58
58
  python-preference = "only-managed"
59
59
  dev-dependencies = [
60
60
  "nanobind~=2.4.0",
61
- "setuptools>=75.6",
61
+ "setuptools~=75.7",
62
62
  "build~=1.2.2.post1",
63
63
  "wheel~=0.45.1",
64
64
  "typing-extensions",
65
65
  "python-dotenv",
66
- "numpy>=2.2",
66
+ "numpy",
67
67
  "httpx",
68
68
  "pytest",
69
69
  "ruff",
70
70
  "mypy",
71
+ "pyjwt",
71
72
  ]
72
73
 
73
74
  [tool.ruff]
@@ -8,7 +8,7 @@ from numpy.typing import ArrayLike
8
8
  class Sora:
9
9
  def __init__(self, use_hardware_encoder: bool | None = None, openh264: str | None = None) -> None: ...
10
10
 
11
- def create_connection(self, signaling_urls: list[str], role: str, channel_id: str, client_id: Optional[str] = None, bundle_id: Optional[str] = None, metadata: Optional[dict] = None, signaling_notify_metadata: Optional[dict] = None, audio_source: Optional[SoraTrackInterface] = None, video_source: Optional[SoraTrackInterface] = None, audio_frame_transformer: Optional[SoraAudioFrameTransformer] = None, video_frame_transformer: Optional[SoraVideoFrameTransformer] = None, audio: Optional[bool] = None, video: Optional[bool] = None, audio_codec_type: Optional[str] = None, video_codec_type: Optional[str] = None, video_bit_rate: Optional[int] = None, audio_bit_rate: Optional[int] = None, video_vp9_params: Optional[dict] = None, video_av1_params: Optional[dict] = None, video_h264_params: Optional[dict] = None, audio_opus_params: Optional[dict] = None, simulcast: Optional[bool] = None, spotlight: Optional[bool] = None, spotlight_number: Optional[int] = None, simulcast_rid: Optional[str] = None, spotlight_focus_rid: Optional[str] = None, spotlight_unfocus_rid: Optional[str] = None, forwarding_filter: Optional[dict] = None, forwarding_filters: Optional[list[dict]] = None, data_channels: Optional[list[dict]] = None, data_channel_signaling: Optional[bool] = None, ignore_disconnect_websocket: Optional[bool] = None, data_channel_signaling_timeout: Optional[int] = None, disconnect_wait_timeout: Optional[int] = None, websocket_close_timeout: Optional[int] = None, websocket_connection_timeout: Optional[int] = None, audio_streaming_language_code: Optional[str] = None, insecure: Optional[bool] = None, client_cert: Optional[bytes] = None, client_key: Optional[bytes] = None, ca_cert: Optional[bytes] = None, proxy_url: Optional[str] = None, proxy_username: Optional[str] = None, proxy_password: Optional[str] = None, proxy_agent: Optional[str] = None) -> SoraConnection: ...
11
+ def create_connection(self, signaling_urls: list[str], role: str, channel_id: str, client_id: Optional[str] = None, bundle_id: Optional[str] = None, metadata: Optional[dict] = None, signaling_notify_metadata: Optional[dict] = None, audio_source: Optional[SoraTrackInterface] = None, video_source: Optional[SoraTrackInterface] = None, audio_frame_transformer: Optional[SoraAudioFrameTransformer] = None, video_frame_transformer: Optional[SoraVideoFrameTransformer] = None, audio: Optional[bool] = None, video: Optional[bool] = None, audio_codec_type: Optional[str] = None, video_codec_type: Optional[str] = None, video_bit_rate: Optional[int] = None, audio_bit_rate: Optional[int] = None, video_vp9_params: Optional[dict] = None, video_av1_params: Optional[dict] = None, video_h264_params: Optional[dict] = None, audio_opus_params: Optional[dict] = None, simulcast: Optional[bool] = None, spotlight: Optional[bool] = None, spotlight_number: Optional[int] = None, simulcast_rid: Optional[str] = None, spotlight_focus_rid: Optional[str] = None, spotlight_unfocus_rid: Optional[str] = None, forwarding_filter: Optional[dict] = None, forwarding_filters: Optional[list[dict]] = None, data_channels: Optional[list[dict]] = None, data_channel_signaling: Optional[bool] = None, ignore_disconnect_websocket: Optional[bool] = None, data_channel_signaling_timeout: Optional[int] = None, disconnect_wait_timeout: Optional[int] = None, websocket_close_timeout: Optional[int] = None, websocket_connection_timeout: Optional[int] = None, audio_streaming_language_code: Optional[str] = None, insecure: Optional[bool] = None, client_cert: Optional[bytes] = None, client_key: Optional[bytes] = None, ca_cert: Optional[bytes] = None, proxy_url: Optional[str] = None, proxy_username: Optional[str] = None, proxy_password: Optional[str] = None, proxy_agent: Optional[str] = None, degradation_preference: Optional[SoraDegradationPreference] = None) -> SoraConnection: ...
12
12
 
13
13
  def create_audio_source(self, channels: int, sample_rate: int) -> SoraAudioSource: ...
14
14
 
@@ -156,6 +156,15 @@ class SoraConnection:
156
156
  @on_data_channel.setter
157
157
  def on_data_channel(self, arg: Callable[[str], None], /) -> None: ...
158
158
 
159
+ class SoraDegradationPreference(enum.IntEnum):
160
+ DISABLED = 0
161
+
162
+ BALANCED = 3
163
+
164
+ MAINTAIN_FRAMERATE = 1
165
+
166
+ MAINTAIN_RESOLUTION = 2
167
+
159
168
  class SoraFrameTransformer:
160
169
  def enqueue(self, arg: SoraTransformableFrame, /) -> None: ...
161
170
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: sora_sdk
3
- Version: 2025.1.0.dev0
3
+ Version: 2025.1.0.dev4
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>
@@ -195,6 +195,7 @@ Requires-Python: >=3.10
195
195
  Description-Content-Type: text/markdown
196
196
  License-File: LICENSE
197
197
  License-File: NOTICE.md
198
+ Dynamic: home-page
198
199
 
199
200
  # Sora Python SDK
200
201
 
@@ -228,6 +229,8 @@ Please read <https://github.com/shiguredo/oss/blob/master/README.en.md> before u
228
229
  - Windows / macOS / Linux (Ubuntu) プラットフォームに対応
229
230
  - [WebRTC 統計情報](https://www.w3.org/TR/webrtc-stats/) の取得が可能
230
231
  - [WebRTC Encoded Transform](https://www.w3.org/TR/webrtc-encoded-transform/) に対応
232
+ - 回線が不安定になった際、解像度とフレームレートどちらを維持するかの設定をする [DegradationPreference](https://w3c.github.io/mst-content-hint/#degradation-preference-when-encoding) に対応
233
+ - MAINTAIN_FRAMERATE / MAINTAIN_RESOLUTION / BALANCED が指定できる
231
234
  - Intel / Apple / NVIDIA のハードウェアデコーダー/エンコーダーに対応
232
235
  - Intel VPL (AV1 / H.264 / H.265)
233
236
  - Apple Video Toolbox (H.264 / H.265)
@@ -282,7 +285,7 @@ PyPI 経由ではインストールできません。
282
285
 
283
286
  ## システム条件
284
287
 
285
- - WebRTC SFU Sora 2023.2.0 以降
288
+ - WebRTC SFU Sora 2024.1.0 以降
286
289
  - Python 3.10 以上
287
290
 
288
291
  ## 対応プラットフォーム
@@ -319,12 +322,10 @@ PyPI 経由ではインストールできません。
319
322
  ### 優先実装が可能な機能一覧
320
323
 
321
324
  - Windows 11 arm64
322
- - macOS Sonoma 13 arm64
323
325
  - Ubuntu 22.04 arm64
324
326
  - Ubuntu 20.04 arm64 (NVIDIA Jetson JetPack SDK 5)
325
327
  - AMD Video Core Next (VCN) 対応
326
328
  - VP9 / AV1 / H.264 / H.265
327
- - Python 3.9 以前への対応
328
329
 
329
330
  ## サポートについて
330
331
 
@@ -347,9 +348,9 @@ Discord へお願いします。
347
348
  Apache License 2.0
348
349
 
349
350
  ```text
350
- Copyright 2023-2024, tnoho (Original Author)
351
- Copyright 2023-2024, Wandbox LLC (Original Author)
352
- Copyright 2023-2024, Shiguredo Inc.
351
+ Copyright 2023-2025, tnoho (Original Author)
352
+ Copyright 2023-2025, Wandbox LLC (Original Author)
353
+ Copyright 2023-2025, Shiguredo Inc.
353
354
 
354
355
  Licensed under the Apache License, Version 2.0 (the "License");
355
356
  you may not use this file except in compliance with the License.
@@ -15,7 +15,10 @@ src/sora_sdk.egg-info/PKG-INFO
15
15
  src/sora_sdk.egg-info/SOURCES.txt
16
16
  src/sora_sdk.egg-info/dependency_links.txt
17
17
  src/sora_sdk.egg-info/top_level.txt
18
+ tests/test_authz.py
19
+ tests/test_authz_simulcast.py
18
20
  tests/test_ca_cert.py
21
+ tests/test_degradation_preference.py
19
22
  tests/test_encoded_transform.py
20
23
  tests/test_macos.py
21
24
  tests/test_messaging.py
@@ -0,0 +1,125 @@
1
+ import sys
2
+ import time
3
+ import uuid
4
+
5
+ import jwt
6
+ import pytest
7
+ from client import SoraClient, SoraRole
8
+
9
+
10
+ @pytest.mark.skipif(reason="Sora C++ SDK 側の対応が必要")
11
+ def test_sendonly_authz_video_true(setup):
12
+ """
13
+ - type: connect で audio: true / video: false で繫ぐ
14
+ - 認証成功時の払い出しで audio: false / video: true を払い出す
15
+ """
16
+ signaling_urls = setup.get("signaling_urls")
17
+ channel_id_prefix = setup.get("channel_id_prefix")
18
+ secret = setup.get("secret")
19
+
20
+ channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"
21
+
22
+ access_token = jwt.encode(
23
+ {
24
+ "channel_id": channel_id,
25
+ "audio": False,
26
+ "video": True,
27
+ # 現在時刻 + 300 秒 (5分)
28
+ "exp": int(time.time()) + 300,
29
+ },
30
+ secret,
31
+ algorithm="HS256",
32
+ )
33
+
34
+ sendonly = SoraClient(
35
+ signaling_urls,
36
+ SoraRole.SENDONLY,
37
+ channel_id,
38
+ audio=True,
39
+ video=False,
40
+ metadata={"access_token": access_token},
41
+ )
42
+ sendonly.connect(fake_video=False, fake_audio=True)
43
+
44
+ time.sleep(5)
45
+
46
+ assert sendonly.offer_message is not None
47
+ assert sendonly.offer_message["sdp"] is not None
48
+ assert "VP9" in sendonly.offer_message["sdp"]
49
+
50
+ sendonly_stats = sendonly.get_stats()
51
+
52
+ sendonly.disconnect()
53
+
54
+ # codec が無かったら StopIteration 例外が上がる
55
+ # 統計で video が見つからないので謎挙動になってる
56
+ sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
57
+ assert sendonly_codec_stats["mimeType"] == "video/VP9"
58
+
59
+ # outbound-rtp が無かったら StopIteration 例外が上がる
60
+ outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
61
+ assert outbound_rtp_stats["encoderImplementation"] == "libvpx"
62
+ assert outbound_rtp_stats["bytesSent"] > 0
63
+ assert outbound_rtp_stats["packetsSent"] > 0
64
+
65
+
66
+ @pytest.mark.parametrize(
67
+ "video_codec_params",
68
+ [
69
+ # video_codec, encoder_implementation, decoder_implementation
70
+ ("VP8", "libvpx"),
71
+ ("VP9", "libvpx"),
72
+ ("AV1", "libaom"),
73
+ ],
74
+ )
75
+ def test_sendonly_authz_video_codec_type(setup, video_codec_params):
76
+ video_codec_type, encoder_implementation = video_codec_params
77
+
78
+ signaling_urls = setup.get("signaling_urls")
79
+ channel_id_prefix = setup.get("channel_id_prefix")
80
+ secret = setup.get("secret")
81
+
82
+ channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"
83
+
84
+ access_token = jwt.encode(
85
+ {
86
+ "channel_id": channel_id,
87
+ "video": True,
88
+ "video_codec_type": video_codec_type,
89
+ # 現在時刻 + 300 秒 (5分)
90
+ "exp": int(time.time()) + 300,
91
+ },
92
+ secret,
93
+ algorithm="HS256",
94
+ )
95
+
96
+ sendonly = SoraClient(
97
+ signaling_urls,
98
+ SoraRole.SENDONLY,
99
+ channel_id,
100
+ audio=False,
101
+ video=True,
102
+ metadata={"access_token": access_token},
103
+ )
104
+ sendonly.connect(fake_video=True)
105
+
106
+ time.sleep(5)
107
+
108
+ assert sendonly.offer_message is not None
109
+ assert sendonly.offer_message["sdp"] is not None
110
+ assert video_codec_type in sendonly.offer_message["sdp"]
111
+
112
+ sendonly_stats = sendonly.get_stats()
113
+
114
+ sendonly.disconnect()
115
+
116
+ # codec が無かったら StopIteration 例外が上がる
117
+ # 統計で video が見つからないので謎挙動になってる
118
+ sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
119
+ assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
120
+
121
+ # outbound-rtp が無かったら StopIteration 例外が上がる
122
+ outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
123
+ assert outbound_rtp_stats["encoderImplementation"] == encoder_implementation
124
+ assert outbound_rtp_stats["bytesSent"] > 0
125
+ assert outbound_rtp_stats["packetsSent"] > 0
@@ -0,0 +1,151 @@
1
+ import sys
2
+ import time
3
+ import uuid
4
+
5
+ import jwt
6
+ import pytest
7
+ from client import SoraClient, SoraRole
8
+
9
+
10
+ @pytest.mark.parametrize(
11
+ (
12
+ "video_codec_type",
13
+ "encoder_implementation",
14
+ "video_bit_rate",
15
+ "video_width",
16
+ "video_height",
17
+ ),
18
+ [
19
+ # どうやら scaleResolutionDownTo を指定すると規定されたテーブルのビットレートでは足りない模様
20
+ ("VP8", "libvpx", 2500, 960, 540),
21
+ ("VP9", "libvpx", 2000, 960, 540),
22
+ ],
23
+ )
24
+ def test_simulcast_authz_scale_resolution_to(
25
+ setup,
26
+ video_codec_type,
27
+ encoder_implementation,
28
+ video_bit_rate,
29
+ video_width,
30
+ video_height,
31
+ ):
32
+ signaling_urls = setup.get("signaling_urls")
33
+ channel_id_prefix = setup.get("channel_id_prefix")
34
+ secret = setup.get("secret")
35
+
36
+ channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}"
37
+
38
+ simulcast_encodings = [
39
+ {
40
+ "rid": "r0",
41
+ "active": True,
42
+ "scaleResolutionDownTo": {"maxWidth": 640, "maxHeight": 360},
43
+ "scalabilityMode": "L1T1",
44
+ },
45
+ {
46
+ "rid": "r1",
47
+ "active": True,
48
+ "scaleResolutionDownTo": {"maxWidth": 640, "maxHeight": 360},
49
+ "scalabilityMode": "L1T1",
50
+ },
51
+ {
52
+ "rid": "r2",
53
+ "active": True,
54
+ "scaleResolutionDownTo": {"maxWidth": 640, "maxHeight": 360},
55
+ "scalabilityMode": "L1T1",
56
+ },
57
+ ]
58
+
59
+ access_token = jwt.encode(
60
+ {
61
+ "channel_id": channel_id,
62
+ "video": True,
63
+ "video_codec_type": video_codec_type,
64
+ "video_bit_rate": video_bit_rate,
65
+ "simulcast": True,
66
+ "simulcast_encodings": simulcast_encodings,
67
+ # 現在時刻 + 300 秒 (5分)
68
+ "exp": int(time.time()) + 300,
69
+ },
70
+ secret,
71
+ algorithm="HS256",
72
+ )
73
+
74
+ sendonly = SoraClient(
75
+ signaling_urls,
76
+ SoraRole.SENDONLY,
77
+ channel_id,
78
+ audio=False,
79
+ video=True,
80
+ metadata={"access_token": access_token},
81
+ video_width=video_width,
82
+ video_height=video_height,
83
+ )
84
+ sendonly.connect(fake_video=True)
85
+
86
+ time.sleep(5)
87
+
88
+ # "type": "offer" の SDP で Simulcast があるかどうか
89
+ assert sendonly.offer_message is not None
90
+ assert sendonly.offer_message["sdp"] is not None
91
+ assert video_codec_type in sendonly.offer_message["sdp"]
92
+ assert "a=simulcast:recv r0;r1;r2" in sendonly.offer_message["sdp"]
93
+ sendonly_stats = sendonly.get_stats()
94
+
95
+ sendonly.disconnect()
96
+
97
+ # "type": "answer" の SDP で Simulcast があるかどうか
98
+ assert sendonly.answer_message is not None
99
+ assert "sdp" in sendonly.answer_message
100
+ assert "a=simulcast:send r0;r1;r2" in sendonly.answer_message["sdp"]
101
+
102
+ # codec が無かったら StopIteration 例外が上がる
103
+ sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
104
+ assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
105
+
106
+ # 複数の outbound-rtp 統計情報を取得
107
+ outbound_rtp_stats = [
108
+ s for s in sendonly_stats if s.get("type") == "outbound-rtp" and s.get("kind") == "video"
109
+ ]
110
+ # simulcast_count に関係なく統計情報はかならず 3 本出力される
111
+ # これは SDP で rid で ~r0 とかやる減るはず
112
+ assert len(outbound_rtp_stats) == 3
113
+
114
+ # rid でソート
115
+ sorted_stats = sorted(outbound_rtp_stats, key=lambda x: x.get("rid", ""))
116
+
117
+ for i, s in enumerate(sorted_stats):
118
+ assert s["rid"] == f"r{i}"
119
+ assert s["kind"] == "video"
120
+
121
+ # VP8 の場合は scaleResolutionDownTo を指定すると SimulcastEncoderAdapter が無くなる
122
+ # TODO: 念のため他の挙動も確認すること
123
+ if video_codec_type == "VP9":
124
+ assert "SimulcastEncoderAdapter" in s["encoderImplementation"]
125
+ assert encoder_implementation in s["encoderImplementation"]
126
+
127
+ assert s["keyFramesEncoded"] > 0
128
+ assert s["bytesSent"] > 500
129
+ assert s["packetsSent"] > 10
130
+
131
+ assert s["frameWidth"] == 640
132
+ assert s["frameHeight"] == 352
133
+
134
+ # FIXME:これは libwebrtc 側の挙動を制御できず L1T2 になってしまう
135
+ assert s["scalabilityMode"] == "L1T2"
136
+
137
+ # targetBitrate が指定したビットレートの 90% 以上、100% 以下に収まることを確認
138
+ expected_bitrate = video_bit_rate * 1000
139
+ print(
140
+ s["rid"],
141
+ video_codec_type,
142
+ s["encoderImplementation"],
143
+ expected_bitrate,
144
+ s["targetBitrate"],
145
+ s["frameWidth"],
146
+ s["frameHeight"],
147
+ s["bytesSent"],
148
+ s["packetsSent"],
149
+ )
150
+ # 期待値の 20% 以上、100% 以下に収まることを確認
151
+ assert expected_bitrate * 0.2 <= s["targetBitrate"] <= expected_bitrate