sora-sdk 2025.3.0.dev8__tar.gz → 2025.4.0.dev1__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 (47) hide show
  1. {sora_sdk-2025.3.0.dev8/src/sora_sdk.egg-info → sora_sdk-2025.4.0.dev1}/PKG-INFO +2 -2
  2. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/README.md +1 -1
  3. sora_sdk-2025.4.0.dev1/VERSION +1 -0
  4. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/buildbase.py +292 -3
  5. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/pyproject.toml +2 -3
  6. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk/sora_sdk_ext.cpython-311-darwin.so +0 -0
  7. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1/src/sora_sdk.egg-info}/PKG-INFO +2 -2
  8. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk.egg-info/SOURCES.txt +1 -0
  9. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_amd_amf.py +72 -4
  10. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_intel_vpl.py +101 -152
  11. sora_sdk-2025.4.0.dev1/tests/test_key_frame_request.py +75 -0
  12. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_nvidia_video_codec_sdk.py +65 -0
  13. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_sendonly_recvonly.py +10 -0
  14. sora_sdk-2025.3.0.dev8/VERSION +0 -1
  15. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/LICENSE +0 -0
  16. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/MANIFEST.in +0 -0
  17. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/pypath.py +0 -0
  18. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/run.py +0 -0
  19. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/setup.cfg +0 -0
  20. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/setup.py +0 -0
  21. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk/__init__.py +0 -0
  22. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk/py.typed +0 -0
  23. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk/sora_sdk_ext.pyi +0 -0
  24. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk.egg-info/dependency_links.txt +0 -0
  25. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk.egg-info/top_level.txt +0 -0
  26. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_apple_video_toolbox.py +0 -0
  27. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_authz.py +0 -0
  28. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_authz_simulcast.py +0 -0
  29. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_ca_cert.py +0 -0
  30. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_capability.py +0 -0
  31. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_degradation_preference.py +0 -0
  32. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_encoded_transform.py +0 -0
  33. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_messaging.py +0 -0
  34. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_messaging_header.py +0 -0
  35. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_openh264.py +0 -0
  36. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_openh264_simulcast.py +0 -0
  37. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_opus.py +0 -0
  38. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_re_offer_re_answer_sdp.py +0 -0
  39. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_signaling.py +0 -0
  40. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_signaling_message.py +0 -0
  41. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_signaling_notify.py +0 -0
  42. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_simulcast.py +0 -0
  43. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_sora_disconnect.py +0 -0
  44. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_type_disconnect.py +0 -0
  45. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_type_switched.py +0 -0
  46. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_vad.py +0 -0
  47. {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sora_sdk
3
- Version: 2025.3.0.dev8
3
+ Version: 2025.4.0.dev1
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>
@@ -108,7 +108,7 @@ PyPI 経由ではインストールできません。
108
108
 
109
109
  ## システム条件
110
110
 
111
- - WebRTC SFU Sora 2024.1.0 以降
111
+ - WebRTC SFU Sora 2024.2.0 以降
112
112
  - Python 3.11 以上
113
113
 
114
114
  ## Python サポートポリシー
@@ -88,7 +88,7 @@ PyPI 経由ではインストールできません。
88
88
 
89
89
  ## システム条件
90
90
 
91
- - WebRTC SFU Sora 2024.1.0 以降
91
+ - WebRTC SFU Sora 2024.2.0 以降
92
92
  - Python 3.11 以上
93
93
 
94
94
  ## Python サポートポリシー
@@ -0,0 +1 @@
1
+ 2025.4.0.dev1
@@ -302,7 +302,12 @@ def _extractzip(z: zipfile.ZipFile, path: str):
302
302
  def extract(file: str, output_dir: str, output_dirname: str, filetype: Optional[str] = None):
303
303
  path = os.path.join(output_dir, output_dirname)
304
304
  logging.info(f"Extract {file} to {path}")
305
- if filetype == "gzip" or file.endswith(".tar.gz"):
305
+ if (
306
+ filetype == "gzip"
307
+ or file.endswith(".tar.gz")
308
+ or filetype == "lzma"
309
+ or file.endswith(".tar.xz")
310
+ ):
306
311
  rm_rf(path)
307
312
  with tarfile.open(file) as t:
308
313
  dir = is_single_dir_tar(t)
@@ -378,6 +383,7 @@ def git_clone_shallow(url, hash, dir, submodule=False):
378
383
 
379
384
 
380
385
  def apply_patch(patch, dir, depth):
386
+ patch = os.path.abspath(patch)
381
387
  with cd(dir):
382
388
  logging.info(f"patch -p{depth} < {patch}")
383
389
  if platform.system() == "Windows":
@@ -800,6 +806,7 @@ def build_and_install_boost(
800
806
  )
801
807
  elif target_os == "android":
802
808
  # Android の場合、android-ndk を使ってビルドする
809
+ # ただし cxx が指定されてた場合はそちらを優先する
803
810
  with open("project-config.jam", "w", encoding="utf-8") as f:
804
811
  bin = os.path.join(
805
812
  android_ndk, "toolchains", "llvm", "prebuilt", android_build_platform, "bin"
@@ -814,7 +821,7 @@ def build_and_install_boost(
814
821
  f.write(
815
822
  f"using clang \
816
823
  : android \
817
- : {escape(os.path.join(bin, 'clang++'))} \
824
+ : {escape(cxx if len(cxx) != 0 else os.path.join(bin, 'clang++'))} \
818
825
  --target=aarch64-none-linux-android{native_api_level} \
819
826
  --sysroot={escape(sysroot)} \
820
827
  : <archiver>{escape(os.path.join(bin, 'llvm-ar'))} \
@@ -932,7 +939,7 @@ def build_sora(
932
939
  ]
933
940
 
934
941
  with cd(local_sora_cpp_sdk_dir):
935
- cmd(["python3", "run.py", platform, *local_sora_cpp_sdk_args])
942
+ cmd(["python3", "run.py", "build", platform, *local_sora_cpp_sdk_args])
936
943
 
937
944
 
938
945
  class SoraInfo(NamedTuple):
@@ -1226,6 +1233,93 @@ def install_sdl2(
1226
1233
  cmd(["cmake", "--install", ".", "--config", configuration])
1227
1234
 
1228
1235
 
1236
+ @versioned
1237
+ def install_sdl3(
1238
+ version, source_dir, build_dir, install_dir, debug: bool, platform: str, cmake_args: List[str]
1239
+ ):
1240
+ url = f"https://github.com/libsdl-org/SDL/releases/download/release-{version}/SDL3-{version}.tar.gz"
1241
+ path = download(url, source_dir)
1242
+ sdl3_source_dir = os.path.join(source_dir, "sdl3")
1243
+ sdl3_build_dir = os.path.join(build_dir, "sdl3")
1244
+ sdl3_install_dir = os.path.join(install_dir, "sdl3")
1245
+ rm_rf(sdl3_source_dir)
1246
+ rm_rf(sdl3_build_dir)
1247
+ rm_rf(sdl3_install_dir)
1248
+ extract(path, source_dir, "sdl3")
1249
+
1250
+ mkdir_p(sdl3_build_dir)
1251
+ with cd(sdl3_build_dir):
1252
+ configuration = "Debug" if debug else "Release"
1253
+ cmake_args = cmake_args[:]
1254
+ cmake_args += [
1255
+ sdl3_source_dir,
1256
+ f"-DCMAKE_BUILD_TYPE={configuration}",
1257
+ f"-DCMAKE_INSTALL_PREFIX={cmake_path(sdl3_install_dir)}",
1258
+ "-DBUILD_SHARED_LIBS=OFF",
1259
+ "-DSDL_STATIC=ON",
1260
+ "-DSDL_SHARED=OFF",
1261
+ ]
1262
+ if platform == "windows":
1263
+ cmake_args += [
1264
+ f"-DCMAKE_MSVC_RUNTIME_LIBRARY={'MultiThreaded' if not debug else 'MultiThreadedDebug'}",
1265
+ "-DSDL_AUDIO=OFF",
1266
+ "-DSDL_JOYSTICK=OFF",
1267
+ "-DSDL_HAPTIC=OFF",
1268
+ # GitHub Actions 上で gameinput.h が存在しないのに
1269
+ # なぜか check_c_source_compiles() が成功してしまうので
1270
+ # HAVE_GAMEINPUT_H=0 で強制的に無効化する
1271
+ "-DHAVE_GAMEINPUT_H=0",
1272
+ ]
1273
+ elif platform == "macos":
1274
+ # どの環境でも同じようにインストールされるようにするため全部 ON/OFF を明示的に指定する
1275
+ cmake_args += [
1276
+ "-DSDL_AUDIO=OFF",
1277
+ "-DSDL_VIDEO=ON",
1278
+ "-DSDL_RENDER=ON",
1279
+ "-DSDL_HAPTIC=ON",
1280
+ "-DSDL_POWER=ON",
1281
+ "-DSDL_JOYSTICK=ON",
1282
+ "-DSDL_SENSOR=ON",
1283
+ "-DSDL_OPENGL=ON",
1284
+ "-DSDL_OPENGLES=ON",
1285
+ "-DSDL_METAL=ON",
1286
+ "-DSDL_VULKAN=OFF",
1287
+ ]
1288
+ elif platform == "linux":
1289
+ # どの環境でも同じようにインストールされるようにするため全部 ON/OFF を明示的に指定する
1290
+ cmake_args += [
1291
+ "-DSDL_AUDIO=OFF",
1292
+ "-DSDL_VIDEO=ON",
1293
+ "-DSDL_RENDER=ON",
1294
+ "-DSDL_HAPTIC=ON",
1295
+ "-DSDL_POWER=ON",
1296
+ "-DSDL_JOYSTICK=ON",
1297
+ "-DSDL_SENSOR=ON",
1298
+ "-DSDL_OPENGL=ON",
1299
+ "-DSDL_OPENGLES=ON",
1300
+ "-DSDL_X11=ON",
1301
+ "-DSDL_X11_SHARED=OFF",
1302
+ "-DSDL_X11_XCURSOR=OFF",
1303
+ "-DSDL_X11_XDBE=OFF",
1304
+ "-DSDL_X11_XFIXES=OFF",
1305
+ "-DSDL_X11_XINPUT=OFF",
1306
+ "-DSDL_X11_XRANDR=OFF",
1307
+ "-DSDL_X11_XSCRNSAVER=OFF",
1308
+ "-DSDL_X11_XSHAPE=OFF",
1309
+ "-DSDL_X11_XSYNC=OFF",
1310
+ "-DSDL_WAYLAND=OFF",
1311
+ "-DSDL_VULKAN=OFF",
1312
+ "-DSDL_KMSDRM=OFF",
1313
+ "-DSDL_RPI=OFF",
1314
+ ]
1315
+ cmd(["cmake"] + cmake_args)
1316
+
1317
+ cmd(
1318
+ ["cmake", "--build", ".", "--config", configuration, f"-j{multiprocessing.cpu_count()}"]
1319
+ )
1320
+ cmd(["cmake", "--install", ".", "--config", configuration])
1321
+
1322
+
1229
1323
  @versioned
1230
1324
  def install_cli11(version, install_dir):
1231
1325
  cli11_install_dir = os.path.join(install_dir, "cli11")
@@ -1307,6 +1401,33 @@ def install_vpl(version, configuration, source_dir, build_dir, install_dir, cmak
1307
1401
  cmd(["cmake", "--install", ".", "--config", configuration])
1308
1402
 
1309
1403
 
1404
+ @versioned
1405
+ def install_blend2d_official(
1406
+ version,
1407
+ configuration,
1408
+ source_dir,
1409
+ build_dir,
1410
+ install_dir,
1411
+ ios,
1412
+ cmake_args,
1413
+ ):
1414
+ rm_rf(os.path.join(source_dir, "blend2d"))
1415
+ rm_rf(os.path.join(build_dir, "blend2d"))
1416
+ rm_rf(os.path.join(install_dir, "blend2d"))
1417
+
1418
+ url = f"https://blend2d.com/download/blend2d-{version}.tar.gz"
1419
+ path = download(url, source_dir)
1420
+ extract(path, source_dir, "blend2d")
1421
+ _build_blend2d(
1422
+ configuration=configuration,
1423
+ source_dir=source_dir,
1424
+ build_dir=build_dir,
1425
+ install_dir=install_dir,
1426
+ ios=ios,
1427
+ cmake_args=cmake_args,
1428
+ )
1429
+
1430
+
1310
1431
  @versioned
1311
1432
  def install_blend2d(
1312
1433
  version,
@@ -1332,7 +1453,17 @@ def install_blend2d(
1332
1453
  asmjit_version,
1333
1454
  os.path.join(source_dir, "blend2d", "3rdparty", "asmjit"),
1334
1455
  )
1456
+ _build_blend2d(
1457
+ configuration=configuration,
1458
+ source_dir=source_dir,
1459
+ build_dir=build_dir,
1460
+ install_dir=install_dir,
1461
+ ios=ios,
1462
+ cmake_args=cmake_args,
1463
+ )
1464
+
1335
1465
 
1466
+ def _build_blend2d(configuration, source_dir, build_dir, install_dir, ios, cmake_args):
1336
1467
  mkdir_p(os.path.join(build_dir, "blend2d"))
1337
1468
  with cd(os.path.join(build_dir, "blend2d")):
1338
1469
  cmd(
@@ -1747,6 +1878,7 @@ def install_opus(
1747
1878
  "cmake",
1748
1879
  f"-DCMAKE_INSTALL_PREFIX={cmake_path(opus_install_dir)}",
1749
1880
  f"-DCMAKE_BUILD_TYPE={configuration}",
1881
+ "-DCMAKE_POSITION_INDEPENDENT_CODE=ON",
1750
1882
  "-DOPUS_BUILD_SHARED_LIBRARY=OFF",
1751
1883
  "-DOPUS_BUILD_TESTING=OFF",
1752
1884
  "-DOPUS_BUILD_PROGRAMS=OFF",
@@ -1803,6 +1935,163 @@ def install_amf(version, install_dir):
1803
1935
  )
1804
1936
 
1805
1937
 
1938
+ @versioned
1939
+ def install_mbedtls(version, source_dir, build_dir, install_dir, debug, cmake_args):
1940
+ rm_rf(os.path.join(source_dir, "mbedtls"))
1941
+ rm_rf(os.path.join(build_dir, "mbedtls"))
1942
+ rm_rf(os.path.join(install_dir, "mbedtls"))
1943
+ git_clone_shallow(
1944
+ "https://github.com/Mbed-TLS/mbedtls.git",
1945
+ version,
1946
+ os.path.join(source_dir, "mbedtls"),
1947
+ )
1948
+ with cd(os.path.join(source_dir, "mbedtls")):
1949
+ configuration = "Debug" if debug else "Release"
1950
+ cmd(["python3", "scripts/config.py", "set", "MBEDTLS_SSL_DTLS_SRTP"])
1951
+ cmd(
1952
+ [
1953
+ "cmake",
1954
+ f"-DCMAKE_INSTALL_PREFIX={cmake_path(os.path.join(install_dir, 'mbedtls'))}",
1955
+ f"-DCMAKE_BUILD_TYPE={configuration}",
1956
+ "-DCMAKE_POSITION_INDEPENDENT_CODE=ON",
1957
+ "-B",
1958
+ os.path.join(build_dir, "mbedtls"),
1959
+ ]
1960
+ + cmake_args
1961
+ )
1962
+ cmd(
1963
+ [
1964
+ "cmake",
1965
+ "--build",
1966
+ os.path.join(build_dir, "mbedtls"),
1967
+ f"-j{multiprocessing.cpu_count()}",
1968
+ "--config",
1969
+ "Release",
1970
+ ]
1971
+ )
1972
+ cmd(["cmake", "--install", os.path.join(build_dir, "mbedtls")])
1973
+
1974
+
1975
+ @versioned
1976
+ def install_libjpeg_turbo(version, source_dir, build_dir, install_dir, configuration, cmake_args):
1977
+ libjpeg_source_dir = os.path.join(source_dir, "libjpeg-turbo")
1978
+ libjpeg_build_dir = os.path.join(build_dir, "libjpeg-turbo")
1979
+ libjpeg_install_dir = os.path.join(install_dir, "libjpeg-turbo")
1980
+ rm_rf(libjpeg_source_dir)
1981
+ rm_rf(libjpeg_build_dir)
1982
+ rm_rf(libjpeg_install_dir)
1983
+ git_clone_shallow(
1984
+ "https://github.com/libjpeg-turbo/libjpeg-turbo.git",
1985
+ version,
1986
+ libjpeg_source_dir,
1987
+ )
1988
+ mkdir_p(libjpeg_build_dir)
1989
+ with cd(libjpeg_build_dir):
1990
+ cmd(
1991
+ [
1992
+ "cmake",
1993
+ libjpeg_source_dir,
1994
+ f"-DCMAKE_INSTALL_PREFIX={libjpeg_install_dir}",
1995
+ f"-DCMAKE_BUILD_TYPE={configuration}",
1996
+ "-DENABLE_SHARED=FALSE",
1997
+ "-DENABLE_STATIC=TRUE",
1998
+ "-DCMAKE_BUILD_TYPE=Release",
1999
+ "-DCMAKE_POSITION_INDEPENDENT_CODE=ON",
2000
+ ]
2001
+ + cmake_args
2002
+ )
2003
+ cmd(
2004
+ [
2005
+ "cmake",
2006
+ "--build",
2007
+ libjpeg_build_dir,
2008
+ f"-j{multiprocessing.cpu_count()}",
2009
+ "--config",
2010
+ "Release",
2011
+ ]
2012
+ )
2013
+ cmd(["cmake", "--install", libjpeg_build_dir])
2014
+
2015
+
2016
+ @versioned
2017
+ def install_libyuv(
2018
+ version, source_dir, build_dir, install_dir, libjpeg_turbo_dir, configuration, cmake_args
2019
+ ):
2020
+ libyuv_source_dir = os.path.join(source_dir, "libyuv")
2021
+ libyuv_build_dir = os.path.join(build_dir, "libyuv")
2022
+ libyuv_install_dir = os.path.join(install_dir, "libyuv")
2023
+ rm_rf(libyuv_source_dir)
2024
+ rm_rf(libyuv_build_dir)
2025
+ rm_rf(libyuv_install_dir)
2026
+ git_clone_shallow(
2027
+ "https://chromium.googlesource.com/libyuv/libyuv",
2028
+ version,
2029
+ libyuv_source_dir,
2030
+ )
2031
+ mkdir_p(libyuv_build_dir)
2032
+ with cd(libyuv_build_dir):
2033
+ cmd(
2034
+ [
2035
+ "cmake",
2036
+ libyuv_source_dir,
2037
+ f"-DCMAKE_INSTALL_PREFIX={cmake_path(libyuv_install_dir)}",
2038
+ f"-DCMAKE_BUILD_TYPE={configuration}",
2039
+ f"-DCMAKE_PREFIX_PATH={cmake_path(libjpeg_turbo_dir)}",
2040
+ *cmake_args,
2041
+ ]
2042
+ )
2043
+ cmd(
2044
+ [
2045
+ "cmake",
2046
+ "--build",
2047
+ libyuv_build_dir,
2048
+ f"-j{multiprocessing.cpu_count()}",
2049
+ "--config",
2050
+ "Release",
2051
+ ]
2052
+ )
2053
+ cmd(["cmake", "--install", libyuv_build_dir])
2054
+
2055
+
2056
+ @versioned
2057
+ def install_aom(version, source_dir, build_dir, install_dir, configuration, cmake_args):
2058
+ aom_source_dir = os.path.join(source_dir, "aom")
2059
+ aom_build_dir = os.path.join(build_dir, "aom")
2060
+ aom_install_dir = os.path.join(install_dir, "aom")
2061
+ rm_rf(aom_source_dir)
2062
+ rm_rf(aom_build_dir)
2063
+ rm_rf(aom_install_dir)
2064
+ git_clone_shallow(
2065
+ "https://aomedia.googlesource.com/aom",
2066
+ version,
2067
+ aom_source_dir,
2068
+ )
2069
+ with cd(aom_source_dir):
2070
+ cmd(
2071
+ [
2072
+ "cmake",
2073
+ "-B",
2074
+ aom_build_dir,
2075
+ f"-DCMAKE_INSTALL_PREFIX={cmake_path(aom_install_dir)}",
2076
+ f"-DCMAKE_BUILD_TYPE={configuration}",
2077
+ "-DENABLE_DOCS=OFF",
2078
+ "-DENABLE_TESTS=OFF",
2079
+ *cmake_args,
2080
+ ]
2081
+ )
2082
+ cmd(
2083
+ [
2084
+ "cmake",
2085
+ "--build",
2086
+ aom_build_dir,
2087
+ f"-j{multiprocessing.cpu_count()}",
2088
+ "--config",
2089
+ "Release",
2090
+ ]
2091
+ )
2092
+ cmd(["cmake", "--install", aom_build_dir])
2093
+
2094
+
1806
2095
  class PlatformTarget(object):
1807
2096
  def __init__(self, os, osver, arch, extra=None):
1808
2097
  self.os = os
@@ -26,9 +26,9 @@ build-backend = "setuptools.build_meta"
26
26
  [tool.uv]
27
27
  python-preference = "only-managed"
28
28
  dev-dependencies = [
29
- "nanobind==2.7.0",
29
+ "nanobind==2.8.0",
30
30
  "setuptools==80.9",
31
- "build==1.2.2.post1",
31
+ "build==1.3.0",
32
32
  "wheel==0.45.1",
33
33
  "numpy",
34
34
  "httpx",
@@ -37,7 +37,6 @@ dev-dependencies = [
37
37
  "pyjwt",
38
38
  "pytest-repeat",
39
39
  "ty",
40
- "pydantic-settings",
41
40
  ]
42
41
 
43
42
  [tool.ruff]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sora_sdk
3
- Version: 2025.3.0.dev8
3
+ Version: 2025.4.0.dev1
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>
@@ -108,7 +108,7 @@ PyPI 経由ではインストールできません。
108
108
 
109
109
  ## システム条件
110
110
 
111
- - WebRTC SFU Sora 2024.1.0 以降
111
+ - WebRTC SFU Sora 2024.2.0 以降
112
112
  - Python 3.11 以上
113
113
 
114
114
  ## Python サポートポリシー
@@ -24,6 +24,7 @@ tests/test_capability.py
24
24
  tests/test_degradation_preference.py
25
25
  tests/test_encoded_transform.py
26
26
  tests/test_intel_vpl.py
27
+ tests/test_key_frame_request.py
27
28
  tests/test_messaging.py
28
29
  tests/test_messaging_header.py
29
30
  tests/test_nvidia_video_codec_sdk.py
@@ -2,6 +2,7 @@ import os
2
2
  import time
3
3
 
4
4
  import pytest
5
+ from api import request_key_frame_api
5
6
  from client import (
6
7
  SoraClient,
7
8
  SoraRole,
@@ -60,8 +61,70 @@ def test_amd_amf_available(settings):
60
61
  @pytest.mark.parametrize(
61
62
  "video_codec_type",
62
63
  [
63
- # AV1 は decoder が正常に動作しない
64
- # "AV1",
64
+ "AV1",
65
+ "H264",
66
+ "H265",
67
+ ],
68
+ )
69
+ def test_amd_amf_key_frame_request(settings, video_codec_type):
70
+ sendonly = SoraClient(
71
+ settings,
72
+ SoraRole.SENDONLY,
73
+ audio=False,
74
+ video=True,
75
+ video_codec_type=video_codec_type,
76
+ video_codec_preference=SoraVideoCodecPreference(
77
+ codecs=[
78
+ SoraVideoCodecPreference.Codec(
79
+ type=codec_type_string_to_codec_type(video_codec_type),
80
+ encoder=SoraVideoCodecImplementation.AMD_AMF,
81
+ ),
82
+ ]
83
+ ),
84
+ )
85
+ sendonly.connect(fake_video=True)
86
+
87
+ time.sleep(3)
88
+
89
+ assert sendonly.connection_id is not None
90
+
91
+ # キーフレーム要求 API を 3 秒間隔で 3 回呼び出す
92
+ api_count = 3
93
+ for _ in range(api_count):
94
+ response = request_key_frame_api(
95
+ settings.api_url, sendonly.channel_id, sendonly.connection_id
96
+ )
97
+ assert response.status_code == 200
98
+ time.sleep(3)
99
+
100
+ # 統計を取得する
101
+ sendonly_stats = sendonly.get_stats()
102
+
103
+ sendonly.disconnect()
104
+
105
+ # outbound-rtp が無かったら StopIteration 例外が上がる
106
+ outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
107
+
108
+ print("video_codec_type:", video_codec_type)
109
+ print("keyFramesEncoded:", outbound_rtp_stats["keyFramesEncoded"])
110
+ print("pliCount:", outbound_rtp_stats["pliCount"])
111
+ print(
112
+ "keyFramesEncoded >= pliCount * 0.7:",
113
+ outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7,
114
+ )
115
+
116
+ assert outbound_rtp_stats["keyFramesEncoded"] > api_count
117
+ assert outbound_rtp_stats["pliCount"] >= api_count
118
+
119
+ # PLI カウントの 50% 以上がキーフレームとしてエンコードされることを確認
120
+ assert outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7
121
+
122
+
123
+ @pytest.mark.skipif(os.environ.get("AMD_AMF") is None, reason="AMD AMF でのみ実行する")
124
+ @pytest.mark.parametrize(
125
+ "video_codec_type",
126
+ [
127
+ "AV1",
65
128
  "H264",
66
129
  "H265",
67
130
  ],
@@ -89,6 +152,8 @@ def test_amd_amf_sendonly_recvonly(settings, video_codec_type):
89
152
  )
90
153
  sendonly.connect(fake_video=True)
91
154
 
155
+ time.sleep(5)
156
+
92
157
  recvonly = SoraClient(
93
158
  settings,
94
159
  SoraRole.RECVONLY,
@@ -123,7 +188,6 @@ def test_amd_amf_sendonly_recvonly(settings, video_codec_type):
123
188
 
124
189
  # codec が無かったら StopIteration 例外が上がる
125
190
  sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
126
- # H.264/H.265 が採用されているかどうか確認する
127
191
  assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
128
192
 
129
193
  # outbound-rtp が無かったら StopIteration 例外が上がる
@@ -131,10 +195,13 @@ def test_amd_amf_sendonly_recvonly(settings, video_codec_type):
131
195
  assert outbound_rtp_stats["encoderImplementation"] == "AMF"
132
196
  assert outbound_rtp_stats["bytesSent"] > 0
133
197
  assert outbound_rtp_stats["packetsSent"] > 0
198
+ assert outbound_rtp_stats["keyFramesEncoded"] > 0
199
+ assert outbound_rtp_stats["pliCount"] > 0
200
+ # PLI カウントの 50% 以上がキーフレームとしてエンコードされることを確認
201
+ assert outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.5
134
202
 
135
203
  # codec が無かったら StopIteration 例外が上がる
136
204
  recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
137
- # H.264/H.265 が採用されているかどうか確認する
138
205
  assert recvonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
139
206
 
140
207
  # inbound-rtp が無かったら StopIteration 例外が上がる
@@ -142,6 +209,7 @@ def test_amd_amf_sendonly_recvonly(settings, video_codec_type):
142
209
  assert inbound_rtp_stats["decoderImplementation"] == "AMF"
143
210
  assert inbound_rtp_stats["bytesReceived"] > 0
144
211
  assert inbound_rtp_stats["packetsReceived"] > 0
212
+ assert inbound_rtp_stats["keyFramesDecoded"] > 0
145
213
 
146
214
 
147
215
  @pytest.mark.skipif(os.environ.get("AMD_AMF") is None, reason="AMD AMF でのみ実行する")
@@ -3,6 +3,7 @@ import platform
3
3
  import time
4
4
 
5
5
  import pytest
6
+ from api import request_key_frame_api
6
7
  from client import (
7
8
  SoraClient,
8
9
  SoraRole,
@@ -43,8 +44,8 @@ def test_intel_vpl_available():
43
44
  case SoraVideoCodecType.VP9:
44
45
  assert c.decoder is True
45
46
  # VPL 的に VP9 は利用できるが、
46
- # Sora Python SDK では VPL VP9 Encoder が正常に動作しないため無効
47
- assert c.encoder is False
47
+ # Sora Python SDK では VPL VP9 Encoder が正常に動作しない
48
+ assert c.encoder is True
48
49
  case SoraVideoCodecType.AV1:
49
50
  # チップによって対応指定ないものがあるので判断しない
50
51
  # assert c.decoder is True
@@ -60,6 +61,69 @@ def test_intel_vpl_available():
60
61
  pytest.fail(f"未実装の codec_type: {c.type}")
61
62
 
62
63
 
64
+ @pytest.mark.skipif(os.environ.get("INTEL_VPL") is None, reason="Intel VPL でのみ実行する")
65
+ @pytest.mark.parametrize(
66
+ "video_codec_type",
67
+ [
68
+ "VP9",
69
+ "AV1",
70
+ "H264",
71
+ "H265",
72
+ ],
73
+ )
74
+ def test_intel_vpl_key_frame_request(settings, video_codec_type):
75
+ sendonly = SoraClient(
76
+ settings,
77
+ SoraRole.SENDONLY,
78
+ audio=False,
79
+ video=True,
80
+ video_codec_type=video_codec_type,
81
+ video_codec_preference=SoraVideoCodecPreference(
82
+ codecs=[
83
+ SoraVideoCodecPreference.Codec(
84
+ type=codec_type_string_to_codec_type(video_codec_type),
85
+ encoder=SoraVideoCodecImplementation.INTEL_VPL,
86
+ ),
87
+ ]
88
+ ),
89
+ )
90
+ sendonly.connect(fake_video=True)
91
+
92
+ time.sleep(3)
93
+
94
+ assert sendonly.connection_id is not None
95
+
96
+ # キーフレーム要求 API を 3 秒間隔で 3 回呼び出す
97
+ api_count = 3
98
+ for _ in range(api_count):
99
+ response = request_key_frame_api(
100
+ settings.api_url, sendonly.channel_id, sendonly.connection_id
101
+ )
102
+ assert response.status_code == 200
103
+ time.sleep(3)
104
+
105
+ # 統計を取得する
106
+ sendonly_stats = sendonly.get_stats()
107
+
108
+ sendonly.disconnect()
109
+
110
+ # outbound-rtp が無かったら StopIteration 例外が上がる
111
+ outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
112
+
113
+ # 3 回以上
114
+ assert outbound_rtp_stats["keyFramesEncoded"] > api_count
115
+ assert outbound_rtp_stats["pliCount"] >= api_count
116
+ print("keyFramesEncoded:", outbound_rtp_stats["keyFramesEncoded"])
117
+ print("pliCount:", outbound_rtp_stats["pliCount"])
118
+
119
+ # PLI カウントの 50% 以上がキーフレームとしてエンコードされることを確認
120
+ assert outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7
121
+ print(
122
+ "keyFramesEncoded >= pliCount * 0.7:",
123
+ outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7,
124
+ )
125
+
126
+
63
127
  @pytest.mark.skipif(os.environ.get("INTEL_VPL") is None, reason="Intel VPL でのみ実行する")
64
128
  @pytest.mark.parametrize(
65
129
  (
@@ -73,14 +137,17 @@ def test_intel_vpl_available():
73
137
  # FIXME: AV1 では、解像度が一定数より低くなる場合、エラーになるのでコメントアウトしている
74
138
  [
75
139
  # 1080p
140
+ ("VP9", "libvpl", 1920, 1080, 3),
76
141
  ("AV1", "libvpl", 1920, 1080, 3),
77
142
  ("H264", "libvpl", 1920, 1080, 3),
78
143
  ("H265", "libvpl", 1920, 1080, 3),
79
144
  # 720p
145
+ ("VP9", "libvpl", 1280, 720, 3),
80
146
  ("AV1", "libvpl", 1280, 720, 3),
81
147
  ("H264", "libvpl", 1280, 720, 3),
82
148
  ("H265", "libvpl", 1280, 720, 3),
83
149
  # 540p
150
+ ("VP9", "libvpl", 960, 540, 3),
84
151
  ("AV1", "libvpl", 960, 540, 3),
85
152
  ("H264", "libvpl", 960, 540, 3),
86
153
  ("H265", "libvpl", 960, 540, 3),
@@ -139,7 +206,7 @@ def test_intel_vpl_simulcast(
139
206
  )
140
207
  sendonly.connect(fake_video=True)
141
208
 
142
- time.sleep(10)
209
+ time.sleep(3)
143
210
 
144
211
  sendonly_stats = sendonly.get_stats()
145
212
 
@@ -218,6 +285,7 @@ def test_intel_vpl_simulcast(
218
285
  @pytest.mark.parametrize(
219
286
  "video_codec_type",
220
287
  [
288
+ "VP9",
221
289
  "AV1",
222
290
  "H264",
223
291
  "H265",
@@ -246,6 +314,8 @@ def test_intel_vpl_sendonly_recvonly(settings, video_codec_type):
246
314
  )
247
315
  sendonly.connect(fake_video=True)
248
316
 
317
+ time.sleep(3)
318
+
249
319
  recvonly = SoraClient(
250
320
  settings,
251
321
  SoraRole.RECVONLY,
@@ -260,7 +330,7 @@ def test_intel_vpl_sendonly_recvonly(settings, video_codec_type):
260
330
  )
261
331
  recvonly.connect()
262
332
 
263
- time.sleep(5)
333
+ time.sleep(3)
264
334
 
265
335
  sendonly_stats = sendonly.get_stats()
266
336
  recvonly_stats = recvonly.get_stats()
@@ -346,7 +416,7 @@ def test_intel_vpl_av1_mini_resolution(
346
416
  )
347
417
  sendonly.connect(fake_video=True)
348
418
 
349
- time.sleep(5)
419
+ time.sleep(3)
350
420
 
351
421
  assert sendonly.connect_message is not None
352
422
  assert sendonly.connect_message["channel_id"] == settings.channel_id
@@ -384,165 +454,39 @@ def test_intel_vpl_av1_mini_resolution(
384
454
  assert outbound_rtp_stats["packetsSent"] > 0
385
455
 
386
456
 
387
- @pytest.mark.skipif(os.environ.get("INTEL_VPL") is None, reason="Intel VPL でのみ実行する")
388
- def test_intel_vpl_decoding_av1(settings):
389
- """
390
- N100 などは AV1 のデコーディングに対応している
391
- """
392
- sendonly = SoraClient(
393
- settings,
394
- SoraRole.SENDONLY,
395
- audio=False,
396
- video=True,
397
- video_codec_type="AV1",
398
- video_codec_preference=SoraVideoCodecPreference(
399
- codecs=[
400
- SoraVideoCodecPreference.Codec(
401
- type=SoraVideoCodecType.AV1,
402
- encoder=SoraVideoCodecImplementation.INTERNAL,
403
- ),
404
- ]
405
- ),
406
- )
407
- sendonly.connect(fake_video=True)
408
-
409
- recvonly = SoraClient(
410
- settings,
411
- SoraRole.RECVONLY,
412
- video_codec_preference=SoraVideoCodecPreference(
413
- codecs=[
414
- SoraVideoCodecPreference.Codec(
415
- type=SoraVideoCodecType.AV1,
416
- decoder=SoraVideoCodecImplementation.INTEL_VPL,
417
- ),
418
- ]
419
- ),
420
- )
421
- recvonly.connect()
422
-
423
- time.sleep(5)
424
-
425
- sendonly_stats = sendonly.get_stats()
426
- recvonly_stats = recvonly.get_stats()
427
-
428
- sendonly.disconnect()
429
- recvonly.disconnect()
430
-
431
- # offer の sdp に video_codec_type が含まれているかどうかを確認している
432
- assert sendonly.offer_message is not None
433
- assert "sdp" in sendonly.offer_message
434
- assert "AV1" in sendonly.offer_message["sdp"]
435
-
436
- # answer の sdp に video_codec_type が含まれているかどうかを確認している
437
- assert sendonly.answer_message is not None
438
- assert "sdp" in sendonly.answer_message
439
- assert "AV1" in sendonly.answer_message["sdp"]
440
-
441
- # codec が無かったら StopIteration 例外が上がる
442
- sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
443
- assert sendonly_codec_stats["mimeType"] == "video/AV1"
444
-
445
- # outbound-rtp が無かったら StopIteration 例外が上がる
446
- outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
447
- assert outbound_rtp_stats["encoderImplementation"] == "libaom"
448
- assert outbound_rtp_stats["bytesSent"] > 0
449
- assert outbound_rtp_stats["packetsSent"] > 0
450
-
451
- # codec が無かったら StopIteration 例外が上がる
452
- recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
453
- assert recvonly_codec_stats["mimeType"] == "video/AV1"
454
-
455
- # inbound-rtp が無かったら StopIteration 例外が上がる
456
- inbound_rtp_stats = next(s for s in recvonly_stats if s.get("type") == "inbound-rtp")
457
- assert inbound_rtp_stats["decoderImplementation"] == "libvpl"
458
- assert inbound_rtp_stats["bytesReceived"] > 0
459
- assert inbound_rtp_stats["packetsReceived"] > 0
460
-
461
-
462
- ## VP9
457
+ ## VPL Decode
463
458
 
464
459
 
465
460
  @pytest.mark.skipif(os.environ.get("INTEL_VPL") is None, reason="Intel VPL でのみ実行する")
466
- @pytest.mark.xfail(
467
- strict=True, reason="VP9 は C++ SDK の Intel VPL で対応できていないのでテストが失敗する"
468
- )
469
461
  @pytest.mark.parametrize(
470
462
  (
471
463
  "video_codec_type",
472
- "expected_implementation",
464
+ "encoder_implementation",
465
+ "decoder_implementation",
473
466
  ),
474
467
  [
475
- ("VP9", "libvpl"),
468
+ ("VP9", "libvpx", "libvpl"),
469
+ ("AV1", "libaom", "libvpl"),
476
470
  ],
477
471
  )
478
- def test_intel_vpl_vp9_failed(settings, video_codec_type, expected_implementation):
479
- sendonly = SoraClient(
480
- settings,
481
- SoraRole.SENDONLY,
482
- audio=False,
483
- video=True,
484
- video_codec_type=video_codec_type,
485
- video_codec_preference=SoraVideoCodecPreference(
486
- codecs=[
487
- SoraVideoCodecPreference.Codec(
488
- type=SoraVideoCodecType.VP9,
489
- encoder=SoraVideoCodecImplementation.INTEL_VPL,
490
- ),
491
- ]
492
- ),
493
- )
494
- sendonly.connect(fake_video=True)
495
-
496
- time.sleep(5)
497
-
498
- assert sendonly.connect_message is not None
499
- assert sendonly.connect_message["channel_id"] == settings.channel_id
500
- assert "video" in sendonly.connect_message
501
- assert sendonly.connect_message["video"]["codec_type"] == video_codec_type
502
-
503
- # offer の sdp に video_codec_type が含まれているかどうかを確認している
504
- assert sendonly.offer_message is not None
505
- assert "sdp" in sendonly.offer_message
506
- assert video_codec_type in sendonly.offer_message["sdp"]
507
-
508
- # answer の sdp に video_codec_type が含まれているかどうかを確認している
509
- assert sendonly.answer_message is not None
510
- assert "sdp" in sendonly.answer_message
511
- assert video_codec_type in sendonly.answer_message["sdp"]
512
-
513
- sendonly_stats = sendonly.get_stats()
514
-
515
- sendonly.disconnect()
516
-
517
- # codec が無かったら StopIteration 例外が上がる
518
- codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
519
- # VP9 が採用されているかどうか確認する
520
- assert codec_stats["mimeType"] == f"video/{video_codec_type}"
521
-
522
- # outbound-rtp が無かったら StopIteration 例外が上がる
523
- outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
524
- # ここで libvpx になって失敗する
525
- assert outbound_rtp_stats["encoderImplementation"] == expected_implementation
526
- assert outbound_rtp_stats["bytesSent"] > 0
527
- assert outbound_rtp_stats["packetsSent"] > 0
528
-
529
-
530
- @pytest.mark.skipif(os.environ.get("INTEL_VPL") is None, reason="Intel VPL でのみ実行する")
531
- def test_intel_vpl_decoding_vp9(settings):
472
+ def test_intel_vpl_decode(
473
+ settings, video_codec_type, encoder_implementation, decoder_implementation
474
+ ):
532
475
  """
533
- VPL VP9 はデコーダーは利用できるので、そのテスト
476
+ * N100 などは AV1 のデコーディングに対応している
477
+ * VPL VP9 はデコーダーは利用できるので、そのテスト
534
478
  """
535
479
  sendonly = SoraClient(
536
480
  settings,
537
481
  SoraRole.SENDONLY,
538
482
  audio=False,
539
483
  video=True,
540
- video_codec_type="VP9",
484
+ video_codec_type=video_codec_type,
541
485
  video_codec_preference=SoraVideoCodecPreference(
542
486
  codecs=[
543
487
  SoraVideoCodecPreference.Codec(
544
- type=SoraVideoCodecType.VP9,
545
- # VPL で VP9 Encoder は正常に動作しないので無効化しているので INTERNAL を指定
488
+ type=codec_type_string_to_codec_type(video_codec_type),
489
+ # エンコーダーはソフトウェアを利用する
546
490
  encoder=SoraVideoCodecImplementation.INTERNAL,
547
491
  ),
548
492
  ]
@@ -550,13 +494,15 @@ def test_intel_vpl_decoding_vp9(settings):
550
494
  )
551
495
  sendonly.connect(fake_video=True)
552
496
 
497
+ time.sleep(3)
498
+
553
499
  recvonly = SoraClient(
554
500
  settings,
555
501
  SoraRole.RECVONLY,
556
502
  video_codec_preference=SoraVideoCodecPreference(
557
503
  codecs=[
558
504
  SoraVideoCodecPreference.Codec(
559
- type=SoraVideoCodecType.VP9,
505
+ type=codec_type_string_to_codec_type(video_codec_type),
560
506
  decoder=SoraVideoCodecImplementation.INTEL_VPL,
561
507
  ),
562
508
  ]
@@ -564,7 +510,7 @@ def test_intel_vpl_decoding_vp9(settings):
564
510
  )
565
511
  recvonly.connect()
566
512
 
567
- time.sleep(5)
513
+ time.sleep(3)
568
514
 
569
515
  sendonly_stats = sendonly.get_stats()
570
516
  recvonly_stats = recvonly.get_stats()
@@ -575,31 +521,34 @@ def test_intel_vpl_decoding_vp9(settings):
575
521
  # offer の sdp に video_codec_type が含まれているかどうかを確認している
576
522
  assert sendonly.offer_message is not None
577
523
  assert "sdp" in sendonly.offer_message
578
- assert "VP9" in sendonly.offer_message["sdp"]
524
+ assert video_codec_type in sendonly.offer_message["sdp"]
579
525
 
580
526
  # answer の sdp に video_codec_type が含まれているかどうかを確認している
581
527
  assert sendonly.answer_message is not None
582
528
  assert "sdp" in sendonly.answer_message
583
- assert "VP9" in sendonly.answer_message["sdp"]
529
+ assert video_codec_type in sendonly.answer_message["sdp"]
584
530
 
585
531
  # codec が無かったら StopIteration 例外が上がる
586
532
  sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
587
533
  # VP9 が採用されているかどうか確認する
588
- assert sendonly_codec_stats["mimeType"] == "video/VP9"
534
+ assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
589
535
 
590
536
  # outbound-rtp が無かったら StopIteration 例外が上がる
591
537
  outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
592
- assert outbound_rtp_stats["encoderImplementation"] == "libvpx"
538
+ assert outbound_rtp_stats["encoderImplementation"] == encoder_implementation
593
539
  assert outbound_rtp_stats["bytesSent"] > 0
594
540
  assert outbound_rtp_stats["packetsSent"] > 0
541
+ assert outbound_rtp_stats["keyFramesEncoded"] > 0
542
+ assert outbound_rtp_stats["pliCount"] > 0
595
543
 
596
544
  # codec が無かったら StopIteration 例外が上がる
597
545
  recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
598
546
  # VP9 が採用されているかどうか確認する
599
- assert recvonly_codec_stats["mimeType"] == "video/VP9"
547
+ assert recvonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
600
548
 
601
549
  # inbound-rtp が無かったら StopIteration 例外が上がる
602
550
  inbound_rtp_stats = next(s for s in recvonly_stats if s.get("type") == "inbound-rtp")
603
- assert inbound_rtp_stats["decoderImplementation"] == "libvpl"
551
+ assert inbound_rtp_stats["decoderImplementation"] == decoder_implementation
604
552
  assert inbound_rtp_stats["bytesReceived"] > 0
605
553
  assert inbound_rtp_stats["packetsReceived"] > 0
554
+ assert inbound_rtp_stats["keyFramesDecoded"] > 0
@@ -0,0 +1,75 @@
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
+ )
@@ -2,6 +2,7 @@ import os
2
2
  import time
3
3
 
4
4
  import pytest
5
+ from api import request_key_frame_api
5
6
  from client import (
6
7
  SoraClient,
7
8
  SoraRole,
@@ -56,6 +57,70 @@ def test_nvidia_video_codec_sdk_available():
56
57
  pytest.fail(f"未実装の codec_type: {c.type}")
57
58
 
58
59
 
60
+ @pytest.mark.skipif(
61
+ os.environ.get("NVIDIA_VIDEO_CODEC_SDK") is None, reason="NVIDIA Video Codec SDK でのみ実行する"
62
+ )
63
+ @pytest.mark.parametrize(
64
+ "video_codec_type",
65
+ [
66
+ "AV1",
67
+ "H264",
68
+ "H265",
69
+ ],
70
+ )
71
+ def test_intel_vpl_key_frame_request(settings, video_codec_type):
72
+ sendonly = SoraClient(
73
+ settings,
74
+ SoraRole.SENDONLY,
75
+ audio=False,
76
+ video=True,
77
+ video_codec_type=video_codec_type,
78
+ video_codec_preference=SoraVideoCodecPreference(
79
+ codecs=[
80
+ SoraVideoCodecPreference.Codec(
81
+ type=codec_type_string_to_codec_type(video_codec_type),
82
+ encoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
83
+ ),
84
+ ]
85
+ ),
86
+ )
87
+ sendonly.connect(fake_video=True)
88
+
89
+ time.sleep(3)
90
+
91
+ assert sendonly.connection_id is not None
92
+
93
+ # キーフレーム要求 API を 3 秒間隔で 3 回呼び出す
94
+ api_count = 3
95
+ for _ in range(api_count):
96
+ response = request_key_frame_api(
97
+ settings.api_url, sendonly.channel_id, sendonly.connection_id
98
+ )
99
+ assert response.status_code == 200
100
+ time.sleep(3)
101
+
102
+ # 統計を取得する
103
+ sendonly_stats = sendonly.get_stats()
104
+
105
+ sendonly.disconnect()
106
+
107
+ # outbound-rtp が無かったら StopIteration 例外が上がる
108
+ outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
109
+
110
+ # 3 回以上
111
+ assert outbound_rtp_stats["keyFramesEncoded"] > api_count
112
+ assert outbound_rtp_stats["pliCount"] >= api_count
113
+ print("keyFramesEncoded:", outbound_rtp_stats["keyFramesEncoded"])
114
+ print("pliCount:", outbound_rtp_stats["pliCount"])
115
+
116
+ # PLI カウントの 50% 以上がキーフレームとしてエンコードされることを確認
117
+ assert outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7
118
+ print(
119
+ "keyFramesEncoded >= pliCount * 0.7:",
120
+ outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7,
121
+ )
122
+
123
+
59
124
  @pytest.mark.skipif(
60
125
  os.environ.get("NVIDIA_VIDEO_CODEC_SDK") is None, reason="NVIDIA Video Codec SDK でのみ実行する"
61
126
  )
@@ -68,6 +68,8 @@ def test_sendonly_recvonly_video(
68
68
  )
69
69
  sendonly.connect(fake_video=True)
70
70
 
71
+ time.sleep(5)
72
+
71
73
  recvonly = SoraClient(
72
74
  settings,
73
75
  SoraRole.RECVONLY,
@@ -91,6 +93,11 @@ def test_sendonly_recvonly_video(
91
93
  assert outbound_rtp_stats["encoderImplementation"] == encoder_implementation
92
94
  assert outbound_rtp_stats["bytesSent"] > 0
93
95
  assert outbound_rtp_stats["packetsSent"] > 0
96
+ assert outbound_rtp_stats["keyFramesEncoded"] > 0
97
+ assert outbound_rtp_stats["pliCount"] > 0
98
+
99
+ print("keyFramesEncoded:", outbound_rtp_stats["keyFramesEncoded"])
100
+ print("pliCount:", outbound_rtp_stats["pliCount"])
94
101
 
95
102
  # codec が無かったら StopIteration 例外が上がる
96
103
  recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
@@ -101,3 +108,6 @@ def test_sendonly_recvonly_video(
101
108
  assert inbound_rtp_stats["decoderImplementation"] == decoder_implementation
102
109
  assert inbound_rtp_stats["bytesReceived"] > 0
103
110
  assert inbound_rtp_stats["packetsReceived"] > 0
111
+ assert inbound_rtp_stats["keyFramesDecoded"] > 0
112
+
113
+ print("keyFrameDecoded:", inbound_rtp_stats["keyFramesDecoded"])
@@ -1 +0,0 @@
1
- 2025.3.0.dev8