sora-sdk 2026.2.0.dev1__tar.gz → 2026.2.0.dev3__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.
- {sora_sdk-2026.2.0.dev1/src/sora_sdk.egg-info → sora_sdk-2026.2.0.dev3}/PKG-INFO +1 -1
- sora_sdk-2026.2.0.dev3/VERSION +1 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/pyproject.toml +1 -1
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/setup.py +6 -1
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/src/sora_sdk/sora_sdk_ext.cpython-312-darwin.so +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/src/sora_sdk/sora_sdk_ext.pyi +4 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3/src/sora_sdk.egg-info}/PKG-INFO +1 -1
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/src/sora_sdk.egg-info/SOURCES.txt +1 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_amd_amf.py +20 -1
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_field_trials.py +48 -10
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_intel_vpl.py +20 -1
- sora_sdk-2026.2.0.dev3/tests/test_key_frame_request.py +131 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_nvidia_video_codec.py +21 -2
- sora_sdk-2026.2.0.dev3/tests/test_rpc.py +202 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_type_disconnect.py +1 -1
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_type_switched.py +5 -1
- sora_sdk-2026.2.0.dev1/VERSION +0 -1
- sora_sdk-2026.2.0.dev1/tests/test_key_frame_request.py +0 -75
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/LICENSE +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/MANIFEST.in +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/README.md +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/buildbase.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/pypath.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/run.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/setup.cfg +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/src/sora_sdk/__init__.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/src/sora_sdk/py.typed +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/src/sora_sdk.egg-info/dependency_links.txt +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/src/sora_sdk.egg-info/top_level.txt +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_apple_video_toolbox.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_audio_sink_callbacks.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_audio_sink_read_gil.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_audio_source_invalid_params.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_authz.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_authz_simulcast.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_ca_cert.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_capability.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_convert_json_value_int64.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_degradation_preference.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_disconnect_timeout.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_encoded_transform.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_messaging.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_messaging_header.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_ndarray_parent_reference.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_openh264.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_openh264_simulcast.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_opus.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_raspberry_pi.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_re_offer_re_answer_sdp.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_rtc_log_refcount.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_send_data_channel_get_stats_after_disconnect.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_sendonly_recvonly.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_signaling.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_signaling_message.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_signaling_notify.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_simulcast.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_sora_disconnect.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_vad.py +0 -0
- {sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/tests/test_version.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2026.2.0.dev3
|
|
@@ -46,7 +46,12 @@ def run_setup(build_platform, target_platform):
|
|
|
46
46
|
def get_tag(self):
|
|
47
47
|
_, _, plat2 = super().get_tag()
|
|
48
48
|
impl = "cp" + sysconfig.get_config_var("py_version_nodot")
|
|
49
|
-
|
|
49
|
+
abi = impl
|
|
50
|
+
# free-threaded ビルド (3.13t 以降) は ABI タグに t を付ける (例: cp313-cp313t)。
|
|
51
|
+
# interpreter タグは cp313 のままで、t は ABI タグにのみ付く
|
|
52
|
+
if sysconfig.get_config_var("Py_GIL_DISABLED"):
|
|
53
|
+
abi += "t"
|
|
54
|
+
return impl, abi, plat if plat is not None else plat2
|
|
50
55
|
|
|
51
56
|
setup(
|
|
52
57
|
version=version,
|
{sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/src/sora_sdk/sora_sdk_ext.cpython-312-darwin.so
RENAMED
|
Binary file
|
|
@@ -25,6 +25,8 @@ class SoraSignalingErrorCode(enum.IntEnum):
|
|
|
25
25
|
|
|
26
26
|
ICE_FAILED = 8
|
|
27
27
|
|
|
28
|
+
DATACHANNEL_CLOSED = 9
|
|
29
|
+
|
|
28
30
|
class SoraSignalingType(enum.IntEnum):
|
|
29
31
|
WEBSOCKET = 0
|
|
30
32
|
|
|
@@ -183,6 +185,8 @@ class SoraConnection:
|
|
|
183
185
|
|
|
184
186
|
def send_data_channel(self, label: str, data: bytes) -> bool: ...
|
|
185
187
|
|
|
188
|
+
def send_rpc(self, id: int | None, method: str, params: object | None = None) -> bool: ...
|
|
189
|
+
|
|
186
190
|
def get_stats(self) -> str: ...
|
|
187
191
|
|
|
188
192
|
@property
|
|
@@ -40,6 +40,7 @@ tests/test_openh264_simulcast.py
|
|
|
40
40
|
tests/test_opus.py
|
|
41
41
|
tests/test_raspberry_pi.py
|
|
42
42
|
tests/test_re_offer_re_answer_sdp.py
|
|
43
|
+
tests/test_rpc.py
|
|
43
44
|
tests/test_rtc_log_refcount.py
|
|
44
45
|
tests/test_send_data_channel_get_stats_after_disconnect.py
|
|
45
46
|
tests/test_sendonly_recvonly.py
|
|
@@ -83,7 +83,25 @@ def test_amd_amf_key_frame_request(settings, video_codec_type):
|
|
|
83
83
|
)
|
|
84
84
|
sendonly.connect(fake_video=True)
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
# RequestKeyFrame はキーフレームを受け取る相手がいる配信者にだけ PLI を送るため、
|
|
87
|
+
# 同じチャンネルに視聴者 (recvonly) を 1 本用意して受信を開始するまで待つ。
|
|
88
|
+
# ハードウェアエンコーダの映像はハードウェアデコーダを指定しないと受信できない
|
|
89
|
+
recvonly = SoraClient(
|
|
90
|
+
settings,
|
|
91
|
+
SoraRole.RECVONLY,
|
|
92
|
+
audio=False,
|
|
93
|
+
video=True,
|
|
94
|
+
video_codec_preference=SoraVideoCodecPreference(
|
|
95
|
+
codecs=[
|
|
96
|
+
SoraVideoCodecPreference.Codec(
|
|
97
|
+
type=codec_type_string_to_codec_type(video_codec_type),
|
|
98
|
+
decoder=SoraVideoCodecImplementation.AMD_AMF,
|
|
99
|
+
),
|
|
100
|
+
]
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
recvonly.connect()
|
|
104
|
+
recvonly.wait_video_received()
|
|
87
105
|
|
|
88
106
|
assert sendonly.connection_id is not None
|
|
89
107
|
|
|
@@ -100,6 +118,7 @@ def test_amd_amf_key_frame_request(settings, video_codec_type):
|
|
|
100
118
|
sendonly_stats = sendonly.get_stats()
|
|
101
119
|
|
|
102
120
|
sendonly.disconnect()
|
|
121
|
+
recvonly.disconnect()
|
|
103
122
|
|
|
104
123
|
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
105
124
|
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
|
|
@@ -64,6 +64,29 @@ def connect_sendonly(settings, field_trials: str | None) -> SoraClient:
|
|
|
64
64
|
return sendonly
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
def connect_recvonly(settings, simulcast_request_rid: str) -> SoraClient:
|
|
68
|
+
"""
|
|
69
|
+
同じチャンネルに視聴者 (recvonly) を接続する。
|
|
70
|
+
|
|
71
|
+
RequestKeyFrame はキーフレームを受け取る相手がいる配信者にだけ、その相手が
|
|
72
|
+
受信している rid へ PLI を送るため、視聴者が受信する rid を明示しておく。
|
|
73
|
+
視聴者の downstream が確定するまで固定で 5 秒待ってから返す (条件待ちはしていない)。
|
|
74
|
+
"""
|
|
75
|
+
recvonly = SoraClient(
|
|
76
|
+
settings,
|
|
77
|
+
SoraRole.RECVONLY,
|
|
78
|
+
audio=False,
|
|
79
|
+
video=True,
|
|
80
|
+
simulcast_request_rid=simulcast_request_rid,
|
|
81
|
+
)
|
|
82
|
+
recvonly.connect()
|
|
83
|
+
|
|
84
|
+
# 視聴者の downstream が確定するまで待つ
|
|
85
|
+
recvonly.wait_video_received()
|
|
86
|
+
|
|
87
|
+
return recvonly
|
|
88
|
+
|
|
89
|
+
|
|
67
90
|
def test_field_trials(settings):
|
|
68
91
|
"""
|
|
69
92
|
WebRTC-Video-PerSsrcKeyframes を指定した Sora でキーフレーム要求が動作すること。
|
|
@@ -74,7 +97,7 @@ def test_field_trials(settings):
|
|
|
74
97
|
|
|
75
98
|
期待:
|
|
76
99
|
- 不正な文字列では RuntimeError になる
|
|
77
|
-
- キーフレーム要求 API
|
|
100
|
+
- キーフレーム要求 API の呼び出しで PLI が届いた rid のキーフレーム数が増える
|
|
78
101
|
"""
|
|
79
102
|
# 文字列の末尾に "/" が無い場合は不正なフィールドトライアル文字列として扱われる
|
|
80
103
|
with pytest.raises(RuntimeError):
|
|
@@ -84,37 +107,50 @@ def test_field_trials(settings):
|
|
|
84
107
|
)
|
|
85
108
|
|
|
86
109
|
sendonly = connect_sendonly(settings, "WebRTC-Video-PerSsrcKeyframes/Enabled/")
|
|
110
|
+
recvonly = connect_recvonly(settings, "r0")
|
|
87
111
|
|
|
88
112
|
# connection_id は None になり得るため、API 呼び出しに使う前に絞り込む
|
|
89
113
|
assert sendonly.connection_id is not None
|
|
90
114
|
|
|
91
|
-
|
|
115
|
+
before_stats = get_active_video_stats_by_rid(sendonly)
|
|
116
|
+
before = get_key_frames_encoded(before_stats)
|
|
92
117
|
print("キーフレーム要求前のキーフレーム数:", before)
|
|
93
118
|
|
|
94
119
|
response = request_key_frame_api(settings.api_url, sendonly.channel_id, sendonly.connection_id)
|
|
95
120
|
|
|
96
121
|
time.sleep(5)
|
|
97
122
|
|
|
98
|
-
|
|
123
|
+
after_stats = get_active_video_stats_by_rid(sendonly)
|
|
124
|
+
after = get_key_frames_encoded(after_stats)
|
|
99
125
|
print("キーフレーム要求後のキーフレーム数:", after)
|
|
100
126
|
|
|
101
127
|
sendonly.disconnect()
|
|
128
|
+
recvonly.disconnect()
|
|
102
129
|
|
|
103
130
|
# 接続を閉じてから確認する。ここで失敗しても接続が残らない
|
|
104
131
|
assert response.status_code == 200, response.text
|
|
105
132
|
|
|
106
133
|
# サイマルキャストの全レイヤが有効になっていること
|
|
107
|
-
assert len(
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
134
|
+
assert len(after_stats) == 3
|
|
135
|
+
# rid を指定しない要求は視聴者が受信している rid に PLI を送る。視聴者が受信している
|
|
136
|
+
# rid は視聴環境によって変わり得るため、PLI が届いた rid を対象にキーフレーム数を確認する
|
|
137
|
+
increased_rids = [
|
|
138
|
+
rid
|
|
139
|
+
for rid in sorted(before_stats)
|
|
140
|
+
if after_stats[rid]["pliCount"] > before_stats[rid]["pliCount"]
|
|
141
|
+
]
|
|
142
|
+
assert increased_rids, "キーフレーム要求で PLI が届いていない"
|
|
143
|
+
for rid in increased_rids:
|
|
144
|
+
assert after[rid] > before[rid], (
|
|
145
|
+
f"rid={rid} のキーフレーム数が増えていない: before={before[rid]}, after={after[rid]}"
|
|
146
|
+
)
|
|
111
147
|
|
|
112
148
|
|
|
113
149
|
@pytest.mark.parametrize(
|
|
114
150
|
("field_trials", "increased_rids"),
|
|
115
151
|
[
|
|
116
152
|
# フィールドトライアル有効時は PLI を受けた rid のレイヤだけがキーフレームを生成する
|
|
117
|
-
("WebRTC-Video-PerSsrcKeyframes/Enabled/", {"
|
|
153
|
+
("WebRTC-Video-PerSsrcKeyframes/Enabled/", {"r0"}),
|
|
118
154
|
# フィールドトライアル無効時はどの rid への PLI でも全レイヤがキーフレームを生成する
|
|
119
155
|
(None, {"r0", "r1", "r2"}),
|
|
120
156
|
],
|
|
@@ -132,7 +168,7 @@ def test_per_ssrc_keyframes(settings, field_trials, increased_rids):
|
|
|
132
168
|
- RequestKeyFrame API の rid 指定は Sora 2026.2.0-canary.15 以降で利用できる
|
|
133
169
|
|
|
134
170
|
期待:
|
|
135
|
-
- フィールドトライアル有効: rid=
|
|
171
|
+
- フィールドトライアル有効: rid=r0 のキーフレーム要求で r0 のみキーフレーム数が増える
|
|
136
172
|
- フィールドトライアル無効: r0 / r1 / r2 すべてのキーフレーム数が増える
|
|
137
173
|
|
|
138
174
|
前提が崩れた場合:
|
|
@@ -141,6 +177,7 @@ def test_per_ssrc_keyframes(settings, field_trials, increased_rids):
|
|
|
141
177
|
いない場合は skip する
|
|
142
178
|
"""
|
|
143
179
|
sendonly = connect_sendonly(settings, field_trials)
|
|
180
|
+
recvonly = connect_recvonly(settings, "r0")
|
|
144
181
|
|
|
145
182
|
# connection_id は None になり得るため、API 呼び出しに使う前に絞り込む
|
|
146
183
|
assert sendonly.connection_id is not None
|
|
@@ -151,7 +188,7 @@ def test_per_ssrc_keyframes(settings, field_trials, increased_rids):
|
|
|
151
188
|
|
|
152
189
|
# rid を指定すると、その rid の SSRC にのみ PLI が送られる
|
|
153
190
|
response = request_key_frame_api(
|
|
154
|
-
settings.api_url, sendonly.channel_id, sendonly.connection_id, rid="
|
|
191
|
+
settings.api_url, sendonly.channel_id, sendonly.connection_id, rid="r0"
|
|
155
192
|
)
|
|
156
193
|
|
|
157
194
|
# Sora はキーフレームを受け取るまで 1 秒間隔で PLI を再送するため、再送が終わるまで待つ
|
|
@@ -162,6 +199,7 @@ def test_per_ssrc_keyframes(settings, field_trials, increased_rids):
|
|
|
162
199
|
print("キーフレーム要求後のキーフレーム数:", key_frames_after)
|
|
163
200
|
|
|
164
201
|
sendonly.disconnect()
|
|
202
|
+
recvonly.disconnect()
|
|
165
203
|
|
|
166
204
|
# 接続を閉じてから確認する。ここで失敗しても接続が残らない
|
|
167
205
|
assert response.status_code == 200, response.text
|
|
@@ -91,7 +91,25 @@ def test_intel_vpl_key_frame_request(settings, video_codec_type):
|
|
|
91
91
|
)
|
|
92
92
|
sendonly.connect(fake_video=True)
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
# RequestKeyFrame はキーフレームを受け取る相手がいる配信者にだけ PLI を送るため、
|
|
95
|
+
# 同じチャンネルに視聴者 (recvonly) を 1 本用意して受信を開始するまで待つ。
|
|
96
|
+
# ハードウェアエンコーダの映像はハードウェアデコーダを指定しないと受信できない
|
|
97
|
+
recvonly = SoraClient(
|
|
98
|
+
settings,
|
|
99
|
+
SoraRole.RECVONLY,
|
|
100
|
+
audio=False,
|
|
101
|
+
video=True,
|
|
102
|
+
video_codec_preference=SoraVideoCodecPreference(
|
|
103
|
+
codecs=[
|
|
104
|
+
SoraVideoCodecPreference.Codec(
|
|
105
|
+
type=codec_type_string_to_codec_type(video_codec_type),
|
|
106
|
+
decoder=SoraVideoCodecImplementation.INTEL_VPL,
|
|
107
|
+
),
|
|
108
|
+
]
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
recvonly.connect()
|
|
112
|
+
recvonly.wait_video_received()
|
|
95
113
|
|
|
96
114
|
assert sendonly.connection_id is not None
|
|
97
115
|
|
|
@@ -108,6 +126,7 @@ def test_intel_vpl_key_frame_request(settings, video_codec_type):
|
|
|
108
126
|
sendonly_stats = sendonly.get_stats()
|
|
109
127
|
|
|
110
128
|
sendonly.disconnect()
|
|
129
|
+
recvonly.disconnect()
|
|
111
130
|
|
|
112
131
|
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
113
132
|
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from api import request_key_frame_api
|
|
5
|
+
from client import (
|
|
6
|
+
SoraClient,
|
|
7
|
+
SoraRole,
|
|
8
|
+
codec_type_string_to_codec_type,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from sora_sdk import (
|
|
12
|
+
SoraVideoCodecImplementation,
|
|
13
|
+
SoraVideoCodecPreference,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.parametrize(
|
|
18
|
+
"video_codec_type",
|
|
19
|
+
[
|
|
20
|
+
"VP9",
|
|
21
|
+
"VP8",
|
|
22
|
+
"AV1",
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
def test_key_frame_request(settings, video_codec_type):
|
|
26
|
+
sendonly = SoraClient(
|
|
27
|
+
settings,
|
|
28
|
+
SoraRole.SENDONLY,
|
|
29
|
+
audio=False,
|
|
30
|
+
video=True,
|
|
31
|
+
video_codec_type=video_codec_type,
|
|
32
|
+
video_codec_preference=SoraVideoCodecPreference(
|
|
33
|
+
codecs=[
|
|
34
|
+
SoraVideoCodecPreference.Codec(
|
|
35
|
+
type=codec_type_string_to_codec_type(video_codec_type),
|
|
36
|
+
encoder=SoraVideoCodecImplementation.INTERNAL,
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
sendonly.connect(fake_video=True)
|
|
42
|
+
|
|
43
|
+
# RequestKeyFrame はキーフレームを受け取る相手がいる配信者にだけ PLI を送るため、
|
|
44
|
+
# 同じチャンネルに視聴者 (recvonly) を 1 本用意して受信を開始するまで待つ
|
|
45
|
+
recvonly = SoraClient(settings, SoraRole.RECVONLY, audio=False, video=True)
|
|
46
|
+
recvonly.connect()
|
|
47
|
+
recvonly.wait_video_received()
|
|
48
|
+
|
|
49
|
+
assert sendonly.connection_id is not None
|
|
50
|
+
|
|
51
|
+
# キーフレーム要求 API を 3 秒間隔で 3 回呼び出す
|
|
52
|
+
api_count = 3
|
|
53
|
+
for _ in range(api_count):
|
|
54
|
+
response = request_key_frame_api(
|
|
55
|
+
settings.api_url, sendonly.channel_id, sendonly.connection_id
|
|
56
|
+
)
|
|
57
|
+
assert response.status_code == 200
|
|
58
|
+
time.sleep(3)
|
|
59
|
+
|
|
60
|
+
# 統計を取得する
|
|
61
|
+
sendonly_stats = sendonly.get_stats()
|
|
62
|
+
|
|
63
|
+
sendonly.disconnect()
|
|
64
|
+
recvonly.disconnect()
|
|
65
|
+
|
|
66
|
+
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
67
|
+
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
|
|
68
|
+
|
|
69
|
+
# 3 回以上
|
|
70
|
+
assert outbound_rtp_stats["keyFramesEncoded"] > api_count
|
|
71
|
+
assert outbound_rtp_stats["pliCount"] >= api_count
|
|
72
|
+
print("keyFramesEncoded:", outbound_rtp_stats["keyFramesEncoded"])
|
|
73
|
+
print("pliCount:", outbound_rtp_stats["pliCount"])
|
|
74
|
+
|
|
75
|
+
# PLI カウントの 50% 以上がキーフレームとしてエンコードされることを確認
|
|
76
|
+
assert outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7
|
|
77
|
+
print(
|
|
78
|
+
"keyFramesEncoded >= pliCount * 0.7:",
|
|
79
|
+
outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def video_outbound_rtp(stats):
|
|
84
|
+
"""outbound-rtp の映像統計を取り出す。無ければ StopIteration 例外が上がる。"""
|
|
85
|
+
return next(s for s in stats if s.get("type") == "outbound-rtp" and s.get("kind") == "video")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_key_frame_request_without_consumer(settings):
|
|
89
|
+
"""
|
|
90
|
+
キーフレームを受け取る相手がいない配信者へのキーフレーム要求では PLI が送られないこと。
|
|
91
|
+
|
|
92
|
+
前提:
|
|
93
|
+
- RequestKeyFrame API はキーフレームを受け取る相手 (視聴者、クラスターのリレー、
|
|
94
|
+
録画、 RTP 転送) がいない配信者には PLI を送らない
|
|
95
|
+
- 送らない場合も API は成功を返す
|
|
96
|
+
|
|
97
|
+
期待:
|
|
98
|
+
- 視聴者がいない状態で API を呼ぶと 200 が返る
|
|
99
|
+
- PLI が送られないため pliCount が増えない
|
|
100
|
+
"""
|
|
101
|
+
sendonly = SoraClient(
|
|
102
|
+
settings,
|
|
103
|
+
SoraRole.SENDONLY,
|
|
104
|
+
audio=False,
|
|
105
|
+
video=True,
|
|
106
|
+
video_codec_type="VP8",
|
|
107
|
+
)
|
|
108
|
+
sendonly.connect(fake_video=True)
|
|
109
|
+
|
|
110
|
+
# 視聴者を用意せず、録画と RTP 転送も使わない状態にする
|
|
111
|
+
time.sleep(5)
|
|
112
|
+
|
|
113
|
+
assert sendonly.connection_id is not None
|
|
114
|
+
|
|
115
|
+
stats_before = video_outbound_rtp(sendonly.get_stats())
|
|
116
|
+
|
|
117
|
+
response = request_key_frame_api(settings.api_url, sendonly.channel_id, sendonly.connection_id)
|
|
118
|
+
|
|
119
|
+
# Sora はキーフレームを受け取るまで 1 秒間隔で最大 5 回 PLI を再送するため、その間待つ
|
|
120
|
+
time.sleep(5)
|
|
121
|
+
|
|
122
|
+
stats_after = video_outbound_rtp(sendonly.get_stats())
|
|
123
|
+
|
|
124
|
+
sendonly.disconnect()
|
|
125
|
+
|
|
126
|
+
# 相手がいない場合も API は成功を返す
|
|
127
|
+
assert response.status_code == 200, response.text
|
|
128
|
+
# キーフレームを受け取る相手がいないため PLI は送られない
|
|
129
|
+
assert stats_after["pliCount"] == stats_before["pliCount"], (
|
|
130
|
+
f"pliCount が増えている: before={stats_before['pliCount']}, after={stats_after['pliCount']}"
|
|
131
|
+
)
|
|
@@ -66,7 +66,7 @@ def test_nvidia_video_codec_sdk_available():
|
|
|
66
66
|
"H265",
|
|
67
67
|
],
|
|
68
68
|
)
|
|
69
|
-
def
|
|
69
|
+
def test_nvidia_video_codec_key_frame_request(settings, video_codec_type):
|
|
70
70
|
sendonly = SoraClient(
|
|
71
71
|
settings,
|
|
72
72
|
SoraRole.SENDONLY,
|
|
@@ -84,7 +84,25 @@ def test_intel_vpl_key_frame_request(settings, video_codec_type):
|
|
|
84
84
|
)
|
|
85
85
|
sendonly.connect(fake_video=True)
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
# RequestKeyFrame はキーフレームを受け取る相手がいる配信者にだけ PLI を送るため、
|
|
88
|
+
# 同じチャンネルに視聴者 (recvonly) を 1 本用意して受信を開始するまで待つ。
|
|
89
|
+
# ハードウェアエンコーダの映像はハードウェアデコーダを指定しないと受信できない
|
|
90
|
+
recvonly = SoraClient(
|
|
91
|
+
settings,
|
|
92
|
+
SoraRole.RECVONLY,
|
|
93
|
+
audio=False,
|
|
94
|
+
video=True,
|
|
95
|
+
video_codec_preference=SoraVideoCodecPreference(
|
|
96
|
+
codecs=[
|
|
97
|
+
SoraVideoCodecPreference.Codec(
|
|
98
|
+
type=codec_type_string_to_codec_type(video_codec_type),
|
|
99
|
+
decoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
|
|
100
|
+
),
|
|
101
|
+
]
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
recvonly.connect()
|
|
105
|
+
recvonly.wait_video_received()
|
|
88
106
|
|
|
89
107
|
assert sendonly.connection_id is not None
|
|
90
108
|
|
|
@@ -101,6 +119,7 @@ def test_intel_vpl_key_frame_request(settings, video_codec_type):
|
|
|
101
119
|
sendonly_stats = sendonly.get_stats()
|
|
102
120
|
|
|
103
121
|
sendonly.disconnect()
|
|
122
|
+
recvonly.disconnect()
|
|
104
123
|
|
|
105
124
|
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
106
125
|
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sora の RPC 機能 (JSON-RPC 2.0 over DataChannel) の E2E テスト。
|
|
3
|
+
|
|
4
|
+
SoraConnection.send_rpc() が rpc ラベルへ JSON-RPC 2.0 のリクエストを送信し、
|
|
5
|
+
応答が on_rpc に届くことを確認する。rpc ラベルは Sora の RPC 機能が有効な場合に
|
|
6
|
+
だけ offer に含まれるため、rpc ラベルを必要とするテストは含まれない環境では
|
|
7
|
+
送信できないことを確認して skip する。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from queue import Empty
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
from client import SoraClient, SoraRole
|
|
17
|
+
|
|
18
|
+
NOT_EXIST_METHOD = "9999.0.0/not_exist"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _create_rpc_client(settings) -> SoraClient:
|
|
22
|
+
"""
|
|
23
|
+
RPC のテストで使うクライアントを生成する (Sora への接続は with 文で行う)。
|
|
24
|
+
|
|
25
|
+
rpc ラベルは DataChannel 経由のシグナリングでのみ使うため data_channel_signaling を
|
|
26
|
+
有効にする。RPC は音声・映像とは独立して使えるため両方無効にするが、この構成では
|
|
27
|
+
ユーザー定義の DataChannel が 1 つも無いと Sora が接続メッセージを 4490
|
|
28
|
+
INVALID-MESSAGE で拒否するため、ダミーの #test ラベルを 1 つ用意する。
|
|
29
|
+
"""
|
|
30
|
+
return SoraClient(
|
|
31
|
+
settings,
|
|
32
|
+
SoraRole.SENDONLY,
|
|
33
|
+
data_channel_signaling=True,
|
|
34
|
+
audio=False,
|
|
35
|
+
video=False,
|
|
36
|
+
data_channels=[{"label": "#test", "direction": "sendonly"}],
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_send_rpc_request_and_response(settings):
|
|
41
|
+
"""
|
|
42
|
+
send_rpc() で送信した JSON-RPC 2.0 のリクエストに応答が返ることを確認する。
|
|
43
|
+
|
|
44
|
+
前提:
|
|
45
|
+
- RPC が有効な Sora に data_channel_signaling を有効にして接続する
|
|
46
|
+
- 払い出される rpc_methods に依存しないよう、存在しないメソッドを指定する
|
|
47
|
+
|
|
48
|
+
期待:
|
|
49
|
+
- send_rpc() が True を返す
|
|
50
|
+
- 応答が on_rpc に届き、送信した id と一致する
|
|
51
|
+
- 存在しないメソッドなので error を含む応答になる
|
|
52
|
+
- params に None / Array を指定した場合と、params を省略した場合も送信できる
|
|
53
|
+
"""
|
|
54
|
+
with _create_rpc_client(settings) as client:
|
|
55
|
+
if not client.wait_rpc_ready(timeout=5):
|
|
56
|
+
# rpc ラベルが使えない環境では送信されず False が返る
|
|
57
|
+
assert client.send_rpc(1, NOT_EXIST_METHOD, {"key": "value"}) is False
|
|
58
|
+
pytest.skip("rpc ラベルが使えない環境のため送信できない")
|
|
59
|
+
|
|
60
|
+
# 送信した id がそのまま応答に含まれることを確認する (0 と 2^64-1 は境界値)
|
|
61
|
+
for request_id in [0, 1, 2**64 - 1]:
|
|
62
|
+
assert client.send_rpc(request_id, NOT_EXIST_METHOD, {"key": "value"})
|
|
63
|
+
|
|
64
|
+
response = client.recv_rpc(timeout=5)
|
|
65
|
+
assert response["id"] == request_id
|
|
66
|
+
assert "error" in response
|
|
67
|
+
|
|
68
|
+
# params に None を指定した場合は params を含めずに送信する
|
|
69
|
+
assert client.send_rpc(2, NOT_EXIST_METHOD, None)
|
|
70
|
+
|
|
71
|
+
response = client.recv_rpc(timeout=5)
|
|
72
|
+
assert response["id"] == 2
|
|
73
|
+
|
|
74
|
+
# params には Object だけでなく Array も指定できる
|
|
75
|
+
assert client.send_rpc(3, NOT_EXIST_METHOD, [])
|
|
76
|
+
|
|
77
|
+
response = client.recv_rpc(timeout=5)
|
|
78
|
+
assert response["id"] == 3
|
|
79
|
+
|
|
80
|
+
# params を省略した 2 引数呼び出しではバインディングの既定値 (None) が使われる。
|
|
81
|
+
# ヘルパは常に params を渡すため、ここでは SDK を直接呼ぶ
|
|
82
|
+
assert client._connection.send_rpc(4, NOT_EXIST_METHOD)
|
|
83
|
+
|
|
84
|
+
response = client.recv_rpc(timeout=5)
|
|
85
|
+
assert response["id"] == 4
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_send_rpc_notification_has_no_response(settings):
|
|
89
|
+
"""
|
|
90
|
+
id に None を指定した Notification には Sora が応答を返さないことを確認する。
|
|
91
|
+
|
|
92
|
+
前提:
|
|
93
|
+
- RPC が有効な Sora に接続する
|
|
94
|
+
- id に None を指定してリクエストを送る
|
|
95
|
+
|
|
96
|
+
期待:
|
|
97
|
+
- send_rpc() が True を返す
|
|
98
|
+
- 応答が返らないため recv_rpc() が Empty でタイムアウトする
|
|
99
|
+
"""
|
|
100
|
+
with _create_rpc_client(settings) as client:
|
|
101
|
+
if not client.wait_rpc_ready(timeout=5):
|
|
102
|
+
pytest.skip("rpc ラベルが使えない環境のため送信できない")
|
|
103
|
+
|
|
104
|
+
assert client.send_rpc(None, NOT_EXIST_METHOD, {"key": "value"})
|
|
105
|
+
|
|
106
|
+
# Notification には Sora が応答を返さない
|
|
107
|
+
with pytest.raises(Empty):
|
|
108
|
+
client.recv_rpc(timeout=3)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_send_rpc_invalid_params(settings):
|
|
112
|
+
"""
|
|
113
|
+
params に送信できない値を指定したときの挙動を確認する。
|
|
114
|
+
|
|
115
|
+
JSON-RPC 2.0 の params は Object か Array でなければならないため、それ以外の値に
|
|
116
|
+
変換される場合は送信せず False が返る。JSON の値として扱えない型の場合は既存の
|
|
117
|
+
Sora::ConvertJsonValue() と同じく TypeError、int64 の範囲を超える整数とキーが
|
|
118
|
+
文字列でない dict は同じく RuntimeError になる。TypeError と RuntimeError は引数の
|
|
119
|
+
変換時に発生するため rpc ラベルの有無に依存しない。False はラベルが無い場合も
|
|
120
|
+
Object / Array 以外の場合も返る。
|
|
121
|
+
"""
|
|
122
|
+
with _create_rpc_client(settings) as client:
|
|
123
|
+
# 文字列・整数・浮動小数点数・真偽値は JSON の値には変換できるが params の要件を満たさない
|
|
124
|
+
for params in ["not-structured", 1, 1.5, True]:
|
|
125
|
+
assert client.send_rpc(1, NOT_EXIST_METHOD, params) is False
|
|
126
|
+
|
|
127
|
+
# JSON の値として扱えない型は TypeError になる
|
|
128
|
+
# 0.1 は float32 で厳密に表現できないため (Sora::ConvertJsonValue() の既知の制限)
|
|
129
|
+
for invalid_params in [
|
|
130
|
+
{"key": set()},
|
|
131
|
+
{"key": (1, 2)},
|
|
132
|
+
{"key": b"bytes"},
|
|
133
|
+
{"key": 0.1},
|
|
134
|
+
]:
|
|
135
|
+
with pytest.raises(TypeError, match="Invalid JSON value in params"):
|
|
136
|
+
client.send_rpc(1, NOT_EXIST_METHOD, invalid_params)
|
|
137
|
+
|
|
138
|
+
# int64 の範囲を超える整数は nb::cast_error 由来の RuntimeError になる
|
|
139
|
+
with pytest.raises(RuntimeError):
|
|
140
|
+
client.send_rpc(1, NOT_EXIST_METHOD, {"key": 2**70})
|
|
141
|
+
|
|
142
|
+
# キーが文字列でない dict も nb::cast_error 由来の RuntimeError になる
|
|
143
|
+
with pytest.raises(RuntimeError):
|
|
144
|
+
client.send_rpc(1, NOT_EXIST_METHOD, {1: "value"})
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_send_rpc_invalid_id(settings):
|
|
148
|
+
"""
|
|
149
|
+
id に None でも 0 以上 2^64-1 以下の整数でもない値を指定すると TypeError になることを確認する。
|
|
150
|
+
|
|
151
|
+
C++ SDK の SendRpc() の id は std::optional<uint64_t> のため、文字列・負の整数・
|
|
152
|
+
2^64 以上の整数は指定できない。
|
|
153
|
+
"""
|
|
154
|
+
with _create_rpc_client(settings) as client:
|
|
155
|
+
# 型注釈 (int | None) では通せない値も実行時に渡すため、Any のリストを経由する
|
|
156
|
+
invalid_ids: list[Any] = ["id", -1, 2**64]
|
|
157
|
+
for invalid_id in invalid_ids:
|
|
158
|
+
with pytest.raises(TypeError):
|
|
159
|
+
client.send_rpc(invalid_id, NOT_EXIST_METHOD, {"key": "value"})
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_send_rpc_without_rpc_label_returns_false(settings):
|
|
163
|
+
"""
|
|
164
|
+
rpc ラベルが使えない接続では send_rpc() が送信せず False を返すことを確認する。
|
|
165
|
+
|
|
166
|
+
前提:
|
|
167
|
+
- DataChannel 経由のシグナリングを使わずに接続する (offer の data_channels に
|
|
168
|
+
rpc ラベルが含まれない)
|
|
169
|
+
|
|
170
|
+
期待:
|
|
171
|
+
- send_rpc() が送信せず False を返す
|
|
172
|
+
- Sora の RPC 機能の有効無効に依存せず検証できる
|
|
173
|
+
"""
|
|
174
|
+
with SoraClient(
|
|
175
|
+
settings,
|
|
176
|
+
SoraRole.RECVONLY,
|
|
177
|
+
audio=True,
|
|
178
|
+
video=True,
|
|
179
|
+
data_channel_signaling=False,
|
|
180
|
+
ignore_disconnect_websocket=False,
|
|
181
|
+
) as client:
|
|
182
|
+
assert client.send_rpc(1, NOT_EXIST_METHOD, {"key": "value"}) is False
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_send_rpc_after_disconnect_raises_runtime_error(settings):
|
|
186
|
+
"""
|
|
187
|
+
disconnect() 後の send_rpc() が SEGV ではなく RuntimeError になることを確認する。
|
|
188
|
+
|
|
189
|
+
前提:
|
|
190
|
+
- Sora に接続してから disconnect() する
|
|
191
|
+
- disconnect() 後の SoraConnection インスタンスをそのまま使う
|
|
192
|
+
|
|
193
|
+
期待:
|
|
194
|
+
- RuntimeError が発生する (メッセージに Already disconnected を含む)
|
|
195
|
+
- プロセスが SEGV で落ちない
|
|
196
|
+
"""
|
|
197
|
+
with _create_rpc_client(settings) as client:
|
|
198
|
+
connection = client._connection
|
|
199
|
+
client.disconnect()
|
|
200
|
+
|
|
201
|
+
with pytest.raises(RuntimeError, match="Already disconnected"):
|
|
202
|
+
connection.send_rpc(1, NOT_EXIST_METHOD, {"key": "value"})
|
|
@@ -27,7 +27,7 @@ def test_websocket_signaling_only_disconnect(settings):
|
|
|
27
27
|
|
|
28
28
|
assert conn.disconnect_code == SoraSignalingErrorCode.CLOSE_SUCCEEDED
|
|
29
29
|
assert (
|
|
30
|
-
conn.disconnect_reason == "Succeeded to close WebSocket (DC signaling is not
|
|
30
|
+
conn.disconnect_reason == "Succeeded to close WebSocket (DC signaling is not available)"
|
|
31
31
|
)
|
|
32
32
|
|
|
33
33
|
|
|
@@ -8,6 +8,8 @@ def test_websocket_signaling_only_type_switched(settings):
|
|
|
8
8
|
"""
|
|
9
9
|
- WebSocket シグナリングのみ
|
|
10
10
|
- type: switched 送られてこない
|
|
11
|
+
- signaling の redirect で張り直した WebSocket のクローズ (SELF-CLOSED) は
|
|
12
|
+
接続中の WebSocket のクローズではないため ws_close には記録されない
|
|
11
13
|
"""
|
|
12
14
|
|
|
13
15
|
with SoraClient(
|
|
@@ -29,6 +31,8 @@ def test_hybrid_signaling_type_switched(settings):
|
|
|
29
31
|
"""
|
|
30
32
|
- WebSocket シグナリング + DataChannel シグナリング
|
|
31
33
|
- type: switched 送られてくる
|
|
34
|
+
- signaling の redirect で張り直した WebSocket のクローズ (SELF-CLOSED) は
|
|
35
|
+
接続中の WebSocket のクローズではないため ws_close には記録されない
|
|
32
36
|
"""
|
|
33
37
|
with SoraClient(
|
|
34
38
|
settings,
|
|
@@ -49,7 +53,7 @@ def test_datachannel_signaling_only_type_switched(settings):
|
|
|
49
53
|
"""
|
|
50
54
|
- DataChannel シグナリングのみ
|
|
51
55
|
- type: switched 送られてくる
|
|
52
|
-
- Python SDK は WebSocket を自分で切断する
|
|
56
|
+
- Python SDK は WebSocket を自分で切断する (switched によるクローズは記録される)
|
|
53
57
|
"""
|
|
54
58
|
with SoraClient(
|
|
55
59
|
settings,
|
sora_sdk-2026.2.0.dev1/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2026.2.0.dev1
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from api import request_key_frame_api
|
|
5
|
-
from client import (
|
|
6
|
-
SoraClient,
|
|
7
|
-
SoraRole,
|
|
8
|
-
codec_type_string_to_codec_type,
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
from sora_sdk import (
|
|
12
|
-
SoraVideoCodecImplementation,
|
|
13
|
-
SoraVideoCodecPreference,
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@pytest.mark.parametrize(
|
|
18
|
-
"video_codec_type",
|
|
19
|
-
[
|
|
20
|
-
"VP9",
|
|
21
|
-
"VP8",
|
|
22
|
-
"AV1",
|
|
23
|
-
],
|
|
24
|
-
)
|
|
25
|
-
def test_key_frame_request(settings, video_codec_type):
|
|
26
|
-
sendonly = SoraClient(
|
|
27
|
-
settings,
|
|
28
|
-
SoraRole.SENDONLY,
|
|
29
|
-
audio=False,
|
|
30
|
-
video=True,
|
|
31
|
-
video_codec_type=video_codec_type,
|
|
32
|
-
video_codec_preference=SoraVideoCodecPreference(
|
|
33
|
-
codecs=[
|
|
34
|
-
SoraVideoCodecPreference.Codec(
|
|
35
|
-
type=codec_type_string_to_codec_type(video_codec_type),
|
|
36
|
-
encoder=SoraVideoCodecImplementation.INTERNAL,
|
|
37
|
-
),
|
|
38
|
-
]
|
|
39
|
-
),
|
|
40
|
-
)
|
|
41
|
-
sendonly.connect(fake_video=True)
|
|
42
|
-
|
|
43
|
-
time.sleep(3)
|
|
44
|
-
|
|
45
|
-
assert sendonly.connection_id is not None
|
|
46
|
-
|
|
47
|
-
# キーフレーム要求 API を 3 秒間隔で 3 回呼び出す
|
|
48
|
-
api_count = 3
|
|
49
|
-
for _ in range(api_count):
|
|
50
|
-
response = request_key_frame_api(
|
|
51
|
-
settings.api_url, sendonly.channel_id, sendonly.connection_id
|
|
52
|
-
)
|
|
53
|
-
assert response.status_code == 200
|
|
54
|
-
time.sleep(3)
|
|
55
|
-
|
|
56
|
-
# 統計を取得する
|
|
57
|
-
sendonly_stats = sendonly.get_stats()
|
|
58
|
-
|
|
59
|
-
sendonly.disconnect()
|
|
60
|
-
|
|
61
|
-
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
62
|
-
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
|
|
63
|
-
|
|
64
|
-
# 3 回以上
|
|
65
|
-
assert outbound_rtp_stats["keyFramesEncoded"] > api_count
|
|
66
|
-
assert outbound_rtp_stats["pliCount"] >= api_count
|
|
67
|
-
print("keyFramesEncoded:", outbound_rtp_stats["keyFramesEncoded"])
|
|
68
|
-
print("pliCount:", outbound_rtp_stats["pliCount"])
|
|
69
|
-
|
|
70
|
-
# PLI カウントの 50% 以上がキーフレームとしてエンコードされることを確認
|
|
71
|
-
assert outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7
|
|
72
|
-
print(
|
|
73
|
-
"keyFramesEncoded >= pliCount * 0.7:",
|
|
74
|
-
outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7,
|
|
75
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sora_sdk-2026.2.0.dev1 → sora_sdk-2026.2.0.dev3}/src/sora_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|