sora-sdk 2025.4.0.dev0__tar.gz → 2025.4.0.dev2__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.4.0.dev0/src/sora_sdk.egg-info → sora_sdk-2025.4.0.dev2}/PKG-INFO +1 -1
  2. sora_sdk-2025.4.0.dev2/VERSION +1 -0
  3. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/buildbase.py +292 -3
  4. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/pyproject.toml +1 -1
  5. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/src/sora_sdk/sora_sdk_ext.cpython-311-darwin.so +0 -0
  6. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2/src/sora_sdk.egg-info}/PKG-INFO +1 -1
  7. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/src/sora_sdk.egg-info/SOURCES.txt +1 -0
  8. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_amd_amf.py +72 -4
  9. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_intel_vpl.py +137 -133
  10. sora_sdk-2025.4.0.dev2/tests/test_key_frame_request.py +75 -0
  11. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_nvidia_video_codec_sdk.py +65 -0
  12. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_sendonly_recvonly.py +10 -0
  13. sora_sdk-2025.4.0.dev0/VERSION +0 -1
  14. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/LICENSE +0 -0
  15. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/MANIFEST.in +0 -0
  16. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/README.md +0 -0
  17. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/pypath.py +0 -0
  18. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/run.py +0 -0
  19. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/setup.cfg +0 -0
  20. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/setup.py +0 -0
  21. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/src/sora_sdk/__init__.py +0 -0
  22. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/src/sora_sdk/py.typed +0 -0
  23. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/src/sora_sdk/sora_sdk_ext.pyi +0 -0
  24. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/src/sora_sdk.egg-info/dependency_links.txt +0 -0
  25. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/src/sora_sdk.egg-info/top_level.txt +0 -0
  26. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_apple_video_toolbox.py +0 -0
  27. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_authz.py +0 -0
  28. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_authz_simulcast.py +0 -0
  29. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_ca_cert.py +0 -0
  30. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_capability.py +0 -0
  31. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_degradation_preference.py +0 -0
  32. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_encoded_transform.py +0 -0
  33. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_messaging.py +0 -0
  34. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_messaging_header.py +0 -0
  35. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_openh264.py +0 -0
  36. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_openh264_simulcast.py +0 -0
  37. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_opus.py +0 -0
  38. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_re_offer_re_answer_sdp.py +0 -0
  39. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_signaling.py +0 -0
  40. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_signaling_message.py +0 -0
  41. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_signaling_notify.py +0 -0
  42. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_simulcast.py +0 -0
  43. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_sora_disconnect.py +0 -0
  44. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_type_disconnect.py +0 -0
  45. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_type_switched.py +0 -0
  46. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_vad.py +0 -0
  47. {sora_sdk-2025.4.0.dev0 → sora_sdk-2025.4.0.dev2}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sora_sdk
3
- Version: 2025.4.0.dev0
3
+ Version: 2025.4.0.dev2
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>
@@ -0,0 +1 @@
1
+ 2025.4.0.dev2
@@ -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
@@ -28,7 +28,7 @@ python-preference = "only-managed"
28
28
  dev-dependencies = [
29
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",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sora_sdk
3
- Version: 2025.4.0.dev0
3
+ Version: 2025.4.0.dev2
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>
@@ -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 get_stats_connection_api, 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,21 +454,39 @@ def test_intel_vpl_av1_mini_resolution(
384
454
  assert outbound_rtp_stats["packetsSent"] > 0
385
455
 
386
456
 
457
+ ## VPL Decode
458
+
459
+
387
460
  @pytest.mark.skipif(os.environ.get("INTEL_VPL") is None, reason="Intel VPL でのみ実行する")
388
- def test_intel_vpl_decoding_av1(settings):
461
+ @pytest.mark.parametrize(
462
+ (
463
+ "video_codec_type",
464
+ "encoder_implementation",
465
+ "decoder_implementation",
466
+ ),
467
+ [
468
+ ("VP9", "libvpx", "libvpl"),
469
+ ("AV1", "libaom", "libvpl"),
470
+ ],
471
+ )
472
+ def test_intel_vpl_decode(
473
+ settings, video_codec_type, encoder_implementation, decoder_implementation
474
+ ):
389
475
  """
390
- N100 などは AV1 のデコーディングに対応している
476
+ * N100 などは AV1 のデコーディングに対応している
477
+ * VPL VP9 はデコーダーは利用できるので、そのテスト
391
478
  """
392
479
  sendonly = SoraClient(
393
480
  settings,
394
481
  SoraRole.SENDONLY,
395
482
  audio=False,
396
483
  video=True,
397
- video_codec_type="AV1",
484
+ video_codec_type=video_codec_type,
398
485
  video_codec_preference=SoraVideoCodecPreference(
399
486
  codecs=[
400
487
  SoraVideoCodecPreference.Codec(
401
- type=SoraVideoCodecType.AV1,
488
+ type=codec_type_string_to_codec_type(video_codec_type),
489
+ # エンコーダーはソフトウェアを利用する
402
490
  encoder=SoraVideoCodecImplementation.INTERNAL,
403
491
  ),
404
492
  ]
@@ -406,13 +494,15 @@ def test_intel_vpl_decoding_av1(settings):
406
494
  )
407
495
  sendonly.connect(fake_video=True)
408
496
 
497
+ time.sleep(3)
498
+
409
499
  recvonly = SoraClient(
410
500
  settings,
411
501
  SoraRole.RECVONLY,
412
502
  video_codec_preference=SoraVideoCodecPreference(
413
503
  codecs=[
414
504
  SoraVideoCodecPreference.Codec(
415
- type=SoraVideoCodecType.AV1,
505
+ type=codec_type_string_to_codec_type(video_codec_type),
416
506
  decoder=SoraVideoCodecImplementation.INTEL_VPL,
417
507
  ),
418
508
  ]
@@ -420,7 +510,7 @@ def test_intel_vpl_decoding_av1(settings):
420
510
  )
421
511
  recvonly.connect()
422
512
 
423
- time.sleep(5)
513
+ time.sleep(3)
424
514
 
425
515
  sendonly_stats = sendonly.get_stats()
426
516
  recvonly_stats = recvonly.get_stats()
@@ -431,61 +521,52 @@ def test_intel_vpl_decoding_av1(settings):
431
521
  # offer の sdp に video_codec_type が含まれているかどうかを確認している
432
522
  assert sendonly.offer_message is not None
433
523
  assert "sdp" in sendonly.offer_message
434
- assert "AV1" in sendonly.offer_message["sdp"]
524
+ assert video_codec_type in sendonly.offer_message["sdp"]
435
525
 
436
526
  # answer の sdp に video_codec_type が含まれているかどうかを確認している
437
527
  assert sendonly.answer_message is not None
438
528
  assert "sdp" in sendonly.answer_message
439
- assert "AV1" in sendonly.answer_message["sdp"]
529
+ assert video_codec_type in sendonly.answer_message["sdp"]
440
530
 
441
531
  # codec が無かったら StopIteration 例外が上がる
442
532
  sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
443
- assert sendonly_codec_stats["mimeType"] == "video/AV1"
533
+ # VP9 が採用されているかどうか確認する
534
+ assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
444
535
 
445
536
  # outbound-rtp が無かったら StopIteration 例外が上がる
446
537
  outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
447
- assert outbound_rtp_stats["encoderImplementation"] == "libaom"
538
+ assert outbound_rtp_stats["encoderImplementation"] == encoder_implementation
448
539
  assert outbound_rtp_stats["bytesSent"] > 0
449
540
  assert outbound_rtp_stats["packetsSent"] > 0
541
+ assert outbound_rtp_stats["keyFramesEncoded"] > 0
542
+ assert outbound_rtp_stats["pliCount"] > 0
450
543
 
451
544
  # codec が無かったら StopIteration 例外が上がる
452
545
  recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
453
- assert recvonly_codec_stats["mimeType"] == "video/AV1"
546
+ # VP9 が採用されているかどうか確認する
547
+ assert recvonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
454
548
 
455
549
  # inbound-rtp が無かったら StopIteration 例外が上がる
456
550
  inbound_rtp_stats = next(s for s in recvonly_stats if s.get("type") == "inbound-rtp")
457
- assert inbound_rtp_stats["decoderImplementation"] == "libvpl"
551
+ assert inbound_rtp_stats["decoderImplementation"] == decoder_implementation
458
552
  assert inbound_rtp_stats["bytesReceived"] > 0
459
553
  assert inbound_rtp_stats["packetsReceived"] > 0
460
-
461
-
462
- ## VP9
554
+ assert inbound_rtp_stats["keyFramesDecoded"] > 0
463
555
 
464
556
 
465
557
  @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
- @pytest.mark.parametrize(
470
- (
471
- "video_codec_type",
472
- "expected_implementation",
473
- ),
474
- [
475
- ("VP9", "libvpl"),
476
- ],
477
- )
478
- def test_intel_vpl_vp9_failed(settings, video_codec_type, expected_implementation):
558
+ def test_intel_vpl_av1_rtp_hdr_ext(settings):
479
559
  sendonly = SoraClient(
480
560
  settings,
481
561
  SoraRole.SENDONLY,
482
562
  audio=False,
483
563
  video=True,
484
- video_codec_type=video_codec_type,
564
+ video_codec_type="AV1",
485
565
  video_codec_preference=SoraVideoCodecPreference(
486
566
  codecs=[
487
567
  SoraVideoCodecPreference.Codec(
488
- type=SoraVideoCodecType.VP9,
568
+ type=codec_type_string_to_codec_type("AV1"),
569
+ # エンコーダーはソフトウェアを利用する
489
570
  encoder=SoraVideoCodecImplementation.INTEL_VPL,
490
571
  ),
491
572
  ]
@@ -493,113 +574,36 @@ def test_intel_vpl_vp9_failed(settings, video_codec_type, expected_implementatio
493
574
  )
494
575
  sendonly.connect(fake_video=True)
495
576
 
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
577
+ time.sleep(3)
502
578
 
503
- # offer sdp に video_codec_type が含まれているかどうかを確認している
579
+ assert sendonly.connection_id is not None
504
580
  assert sendonly.offer_message is not None
505
581
  assert "sdp" in sendonly.offer_message
506
- assert video_codec_type in sendonly.offer_message["sdp"]
582
+ assert "AV1" in sendonly.offer_message["sdp"]
583
+ assert (
584
+ "https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension"
585
+ in sendonly.offer_message["sdp"]
586
+ )
507
587
 
508
- # answer の sdp に video_codec_type が含まれているかどうかを確認している
509
588
  assert sendonly.answer_message is not None
510
589
  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):
532
- """
533
- VPL VP9 はデコーダーは利用できるので、そのテスト
534
- """
535
- sendonly = SoraClient(
536
- settings,
537
- SoraRole.SENDONLY,
538
- audio=False,
539
- video=True,
540
- video_codec_type="VP9",
541
- video_codec_preference=SoraVideoCodecPreference(
542
- codecs=[
543
- SoraVideoCodecPreference.Codec(
544
- type=SoraVideoCodecType.VP9,
545
- # VPL で VP9 Encoder は正常に動作しないので無効化しているので INTERNAL を指定
546
- encoder=SoraVideoCodecImplementation.INTERNAL,
547
- ),
548
- ]
549
- ),
590
+ assert "AV1" in sendonly.answer_message["sdp"]
591
+ assert (
592
+ "https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension"
593
+ in sendonly.answer_message["sdp"]
550
594
  )
551
- sendonly.connect(fake_video=True)
552
595
 
553
- recvonly = SoraClient(
554
- settings,
555
- SoraRole.RECVONLY,
556
- video_codec_preference=SoraVideoCodecPreference(
557
- codecs=[
558
- SoraVideoCodecPreference.Codec(
559
- type=SoraVideoCodecType.VP9,
560
- decoder=SoraVideoCodecImplementation.INTEL_VPL,
561
- ),
562
- ]
563
- ),
596
+ # コネクションの統計情報を取得
597
+ response = get_stats_connection_api(
598
+ settings.api_url, sendonly.channel_id, sendonly.connection_id
564
599
  )
565
- recvonly.connect()
566
-
567
- time.sleep(5)
568
-
569
- sendonly_stats = sendonly.get_stats()
570
- recvonly_stats = recvonly.get_stats()
600
+ # FIX: ここで失敗すると disconnect が実行されずメモリーリークになる
601
+ assert response.status_code == 200
602
+ stats = response.json()
571
603
 
572
604
  sendonly.disconnect()
573
- recvonly.disconnect()
574
605
 
575
- # offersdp に video_codec_type が含まれているかどうかを確認している
576
- assert sendonly.offer_message is not None
577
- assert "sdp" in sendonly.offer_message
578
- assert "VP9" in sendonly.offer_message["sdp"]
579
-
580
- # answer の sdp に video_codec_type が含まれているかどうかを確認している
581
- assert sendonly.answer_message is not None
582
- assert "sdp" in sendonly.answer_message
583
- assert "VP9" in sendonly.answer_message["sdp"]
584
-
585
- # codec が無かったら StopIteration 例外が上がる
586
- sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
587
- # VP9 が採用されているかどうか確認する
588
- assert sendonly_codec_stats["mimeType"] == "video/VP9"
589
-
590
- # outbound-rtp が無かったら StopIteration 例外が上がる
591
- outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
592
- assert outbound_rtp_stats["encoderImplementation"] == "libvpx"
593
- assert outbound_rtp_stats["bytesSent"] > 0
594
- assert outbound_rtp_stats["packetsSent"] > 0
595
-
596
- # codec が無かったら StopIteration 例外が上がる
597
- recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
598
- # VP9 が採用されているかどうか確認する
599
- assert recvonly_codec_stats["mimeType"] == "video/VP9"
600
-
601
- # inbound-rtp が無かったら StopIteration 例外が上がる
602
- inbound_rtp_stats = next(s for s in recvonly_stats if s.get("type") == "inbound-rtp")
603
- assert inbound_rtp_stats["decoderImplementation"] == "libvpl"
604
- assert inbound_rtp_stats["bytesReceived"] > 0
605
- assert inbound_rtp_stats["packetsReceived"] > 0
606
+ # AV1RTP ヘッダー拡張が送られてきていることを確認
607
+ assert stats["rtp_hdrext"]["total_received_rtp_hdrext_av1_rtp_sepc"] > 0, (
608
+ "Dependency Descriptor RTP Header Extension が Python SDK から送られてきていません"
609
+ )
@@ -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.4.0.dev0