libdatachannel-py 2025.1.0.dev6__tar.gz → 2025.1.0.dev8__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.
Files changed (38) hide show
  1. {libdatachannel_py-2025.1.0.dev6/src/libdatachannel_py.egg-info → libdatachannel_py-2025.1.0.dev8}/PKG-INFO +1 -1
  2. libdatachannel_py-2025.1.0.dev8/VERSION +1 -0
  3. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/buildbase.py +123 -2
  4. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/pyproject.toml +5 -5
  5. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/run.py +79 -0
  6. libdatachannel_py-2025.1.0.dev8/src/libdatachannel/libdatachannel_ext.cpython-313-darwin.so +0 -0
  7. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/src/libdatachannel/libdatachannel_ext.pyi +2 -210
  8. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8/src/libdatachannel_py.egg-info}/PKG-INFO +1 -1
  9. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/src/libdatachannel_py.egg-info/SOURCES.txt +2 -0
  10. libdatachannel_py-2025.1.0.dev8/tests/test_audio_codec.py +72 -0
  11. libdatachannel_py-2025.1.0.dev8/tests/test_libyuv.py +200 -0
  12. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_video_codec.py +45 -13
  13. libdatachannel_py-2025.1.0.dev6/VERSION +0 -1
  14. libdatachannel_py-2025.1.0.dev6/src/libdatachannel/libdatachannel_ext.cpython-313-darwin.so +0 -0
  15. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/LICENSE +0 -0
  16. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/MANIFEST.in +0 -0
  17. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/README.md +0 -0
  18. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/pypath.py +0 -0
  19. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/setup.cfg +0 -0
  20. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/setup.py +0 -0
  21. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/src/libdatachannel/__init__.py +0 -0
  22. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/src/libdatachannel/py.typed +0 -0
  23. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/src/libdatachannel_py.egg-info/dependency_links.txt +0 -0
  24. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/src/libdatachannel_py.egg-info/top_level.txt +0 -0
  25. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_candidate.py +0 -0
  26. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_configuration.py +0 -0
  27. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_description.py +0 -0
  28. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_frameinfo.py +0 -0
  29. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_h265nalunit.py +0 -0
  30. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_mediahandler.py +0 -0
  31. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_message.py +0 -0
  32. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_nalunit.py +0 -0
  33. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_packetizationconfig.py +0 -0
  34. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_peerconnection.py +0 -0
  35. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_reliability.py +0 -0
  36. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_rtppacketizer.py +0 -0
  37. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_websocket.py +0 -0
  38. {libdatachannel_py-2025.1.0.dev6 → libdatachannel_py-2025.1.0.dev8}/tests/test_websocketserver.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: libdatachannel-py
3
- Version: 2025.1.0.dev6
3
+ Version: 2025.1.0.dev8
4
4
  Summary: libdatachannel-py is a Python binding for libdatachannel.
5
5
  Home-page: https://github.com/shiguredo/libdatachannel-py
6
6
  License-Expression: Apache-2.0
@@ -0,0 +1 @@
1
+ 2025.1.0.dev8
@@ -702,8 +702,8 @@ def build_and_install_boost(
702
702
  ):
703
703
  version_underscore = version.replace(".", "_")
704
704
  archive = download(
705
- # 公式サイトに負荷をかけないための時雨堂による R2 ミラー
706
- f"https://pub-31f2ce80536a4322af33196843feefc2.r2.dev/boost_{version_underscore}.tar.gz",
705
+ # 公式サイトに負荷をかけないための時雨堂によるミラー
706
+ f"https://oss-mirrors.shiguredo.jp/boost_{version_underscore}.tar.gz",
707
707
  # Boost 公式のミラー
708
708
  # f"https://archives.boost.io/release/{version}/source/boost_{version_underscore}.tar.gz",
709
709
  source_dir,
@@ -1748,6 +1748,7 @@ def install_opus(
1748
1748
  "cmake",
1749
1749
  f"-DCMAKE_INSTALL_PREFIX={cmake_path(opus_install_dir)}",
1750
1750
  f"-DCMAKE_BUILD_TYPE={configuration}",
1751
+ "-DCMAKE_POSITION_INDEPENDENT_CODE=ON",
1751
1752
  "-DOPUS_BUILD_SHARED_LIBRARY=OFF",
1752
1753
  "-DOPUS_BUILD_TESTING=OFF",
1753
1754
  "-DOPUS_BUILD_PROGRAMS=OFF",
@@ -1841,6 +1842,126 @@ def install_mbedtls(version, source_dir, build_dir, install_dir, debug, cmake_ar
1841
1842
  cmd(["cmake", "--install", os.path.join(build_dir, "mbedtls")])
1842
1843
 
1843
1844
 
1845
+ @versioned
1846
+ def install_libjpeg_turbo(version, source_dir, build_dir, install_dir, configuration, cmake_args):
1847
+ libjpeg_source_dir = os.path.join(source_dir, "libjpeg-turbo")
1848
+ libjpeg_build_dir = os.path.join(build_dir, "libjpeg-turbo")
1849
+ libjpeg_install_dir = os.path.join(install_dir, "libjpeg-turbo")
1850
+ rm_rf(libjpeg_source_dir)
1851
+ rm_rf(libjpeg_build_dir)
1852
+ rm_rf(libjpeg_install_dir)
1853
+ git_clone_shallow(
1854
+ "https://github.com/libjpeg-turbo/libjpeg-turbo.git",
1855
+ version,
1856
+ libjpeg_source_dir,
1857
+ )
1858
+ mkdir_p(libjpeg_build_dir)
1859
+ with cd(libjpeg_build_dir):
1860
+ cmd(
1861
+ [
1862
+ "cmake",
1863
+ libjpeg_source_dir,
1864
+ f"-DCMAKE_INSTALL_PREFIX={libjpeg_install_dir}",
1865
+ f"-DCMAKE_BUILD_TYPE={configuration}",
1866
+ "-DENABLE_SHARED=FALSE",
1867
+ "-DENABLE_STATIC=TRUE",
1868
+ "-DCMAKE_BUILD_TYPE=Release",
1869
+ "-DCMAKE_POSITION_INDEPENDENT_CODE=ON",
1870
+ ]
1871
+ + cmake_args
1872
+ )
1873
+ cmd(
1874
+ [
1875
+ "cmake",
1876
+ "--build",
1877
+ libjpeg_build_dir,
1878
+ f"-j{multiprocessing.cpu_count()}",
1879
+ "--config",
1880
+ "Release",
1881
+ ]
1882
+ )
1883
+ cmd(["cmake", "--install", libjpeg_build_dir])
1884
+
1885
+
1886
+ @versioned
1887
+ def install_libyuv(
1888
+ version, source_dir, build_dir, install_dir, libjpeg_turbo_dir, configuration, cmake_args
1889
+ ):
1890
+ libyuv_source_dir = os.path.join(source_dir, "libyuv")
1891
+ libyuv_build_dir = os.path.join(build_dir, "libyuv")
1892
+ libyuv_install_dir = os.path.join(install_dir, "libyuv")
1893
+ rm_rf(libyuv_source_dir)
1894
+ rm_rf(libyuv_build_dir)
1895
+ rm_rf(libyuv_install_dir)
1896
+ git_clone_shallow(
1897
+ "https://chromium.googlesource.com/libyuv/libyuv",
1898
+ version,
1899
+ libyuv_source_dir,
1900
+ )
1901
+ mkdir_p(libyuv_build_dir)
1902
+ with cd(libyuv_build_dir):
1903
+ cmd(
1904
+ [
1905
+ "cmake",
1906
+ libyuv_source_dir,
1907
+ f"-DCMAKE_INSTALL_PREFIX={cmake_path(libyuv_install_dir)}",
1908
+ f"-DCMAKE_BUILD_TYPE={configuration}",
1909
+ f"-DCMAKE_PREFIX_PATH={cmake_path(libjpeg_turbo_dir)}",
1910
+ *cmake_args,
1911
+ ]
1912
+ )
1913
+ cmd(
1914
+ [
1915
+ "cmake",
1916
+ "--build",
1917
+ libyuv_build_dir,
1918
+ f"-j{multiprocessing.cpu_count()}",
1919
+ "--config",
1920
+ "Release",
1921
+ ]
1922
+ )
1923
+ cmd(["cmake", "--install", libyuv_build_dir])
1924
+
1925
+
1926
+ @versioned
1927
+ def install_aom(version, source_dir, build_dir, install_dir, configuration, cmake_args):
1928
+ aom_source_dir = os.path.join(source_dir, "aom")
1929
+ aom_build_dir = os.path.join(build_dir, "aom")
1930
+ aom_install_dir = os.path.join(install_dir, "aom")
1931
+ rm_rf(aom_source_dir)
1932
+ rm_rf(aom_build_dir)
1933
+ rm_rf(aom_install_dir)
1934
+ git_clone_shallow(
1935
+ "https://aomedia.googlesource.com/aom",
1936
+ version,
1937
+ aom_source_dir,
1938
+ )
1939
+ with cd(aom_source_dir):
1940
+ cmd(
1941
+ [
1942
+ "cmake",
1943
+ "-B",
1944
+ aom_build_dir,
1945
+ f"-DCMAKE_INSTALL_PREFIX={cmake_path(aom_install_dir)}",
1946
+ f"-DCMAKE_BUILD_TYPE={configuration}",
1947
+ "-DENABLE_DOCS=OFF",
1948
+ "-DENABLE_TESTS=OFF",
1949
+ *cmake_args,
1950
+ ]
1951
+ )
1952
+ cmd(
1953
+ [
1954
+ "cmake",
1955
+ "--build",
1956
+ aom_build_dir,
1957
+ f"-j{multiprocessing.cpu_count()}",
1958
+ "--config",
1959
+ "Release",
1960
+ ]
1961
+ )
1962
+ cmd(["cmake", "--install", aom_build_dir])
1963
+
1964
+
1844
1965
  class PlatformTarget(object):
1845
1966
  def __init__(self, os, osver, arch, extra=None):
1846
1967
  self.os = os
@@ -18,17 +18,17 @@ Source = "https://github.com/shiguredo/libdatachannel-py"
18
18
  Discord = "https://discord.gg/shiguredo"
19
19
 
20
20
  [build-system]
21
- requires = ["setuptools==80.4", "wheel==0.45.1"]
21
+ requires = ["setuptools>=80.8", "wheel>=0.45.1"]
22
22
  build-backend = "setuptools.build_meta"
23
23
 
24
24
  [dependency-groups]
25
25
  dev = [
26
- "nanobind>=2.7.0",
27
- "pytest>=8.3.5",
28
- "setuptools>=80.4",
26
+ "nanobind>=2.7",
27
+ "pytest>=8",
28
+ "setuptools>=80.8",
29
29
  "build>=1.2.2.post1",
30
30
  "wheel>=0.45.1",
31
- "numpy>=2.2.5",
31
+ "numpy>=2.2",
32
32
  ]
33
33
 
34
34
  [tool.ruff]
@@ -16,9 +16,13 @@ from buildbase import (
16
16
  get_macos_osver,
17
17
  get_windows_osver,
18
18
  git_clone_shallow,
19
+ install_aom,
19
20
  install_cmake,
21
+ install_libjpeg_turbo,
22
+ install_libyuv,
20
23
  install_mbedtls,
21
24
  install_openh264,
25
+ install_opus,
22
26
  mkdir_p,
23
27
  read_version_file,
24
28
  )
@@ -115,6 +119,55 @@ def install_deps(
115
119
  }
116
120
  install_openh264(**install_openh264_args)
117
121
 
122
+ # Opus
123
+ install_opus_args = {
124
+ "version": version["OPUS_VERSION"],
125
+ "version_file": os.path.join(install_dir, "opus.version"),
126
+ "source_dir": source_dir,
127
+ "build_dir": build_dir,
128
+ "install_dir": install_dir,
129
+ "configuration": "Debug" if debug else "Release",
130
+ "cmake_args": macos_cmake_args,
131
+ }
132
+ install_opus(**install_opus_args)
133
+
134
+ # libjpeg-turbo
135
+ install_libjpeg_turbo_args = {
136
+ "version": version["LIBJPEG_TURBO_VERSION"],
137
+ "version_file": os.path.join(install_dir, "libjpeg-turbo.version"),
138
+ "source_dir": source_dir,
139
+ "build_dir": build_dir,
140
+ "install_dir": install_dir,
141
+ "configuration": "Debug" if debug else "Release",
142
+ "cmake_args": macos_cmake_args,
143
+ }
144
+ install_libjpeg_turbo(**install_libjpeg_turbo_args)
145
+
146
+ # libyuv
147
+ install_libyuv_args = {
148
+ "version": version["LIBYUV_VERSION"],
149
+ "version_file": os.path.join(install_dir, "libyuv.version"),
150
+ "source_dir": source_dir,
151
+ "build_dir": build_dir,
152
+ "install_dir": install_dir,
153
+ "libjpeg_turbo_dir": os.path.join(install_dir, "libjpeg-turbo"),
154
+ "configuration": "Debug" if debug else "Release",
155
+ "cmake_args": macos_cmake_args,
156
+ }
157
+ install_libyuv(**install_libyuv_args)
158
+
159
+ # AOM
160
+ install_aom_args = {
161
+ "version": version["AOM_VERSION"],
162
+ "version_file": os.path.join(install_dir, "aom.version"),
163
+ "source_dir": source_dir,
164
+ "build_dir": build_dir,
165
+ "install_dir": install_dir,
166
+ "configuration": "Debug" if debug else "Release",
167
+ "cmake_args": macos_cmake_args,
168
+ }
169
+ install_aom(**install_aom_args)
170
+
118
171
 
119
172
  AVAILABLE_TARGETS = [
120
173
  "windows_x86_64",
@@ -199,6 +252,18 @@ def main():
199
252
  # OpenH264
200
253
  cmake_args.append(f"-DOPENH264_DIR={cmake_path(os.path.join(install_dir, 'openh264'))}")
201
254
 
255
+ # Opus
256
+ cmake_args.append(f"-DOPUS_DIR={cmake_path(os.path.join(install_dir, 'opus'))}")
257
+
258
+ # Libyuv
259
+ cmake_args.append(
260
+ f"-DLIBJPEG_TURBO_DIR={cmake_path(os.path.join(install_dir, 'libjpeg-turbo'))}"
261
+ )
262
+ cmake_args.append(f"-DLIBYUV_DIR={cmake_path(os.path.join(install_dir, 'libyuv'))}")
263
+
264
+ # AOM
265
+ cmake_args.append(f"-DAOM_DIR={cmake_path(os.path.join(install_dir, 'aom'))}")
266
+
202
267
  if platform.target.os == "macos":
203
268
  sysroot = cmdcap(["xcrun", "--sdk", "macosx", "--show-sdk-path"])
204
269
  cmake_args += [
@@ -261,6 +326,20 @@ def main():
261
326
  os.path.join(libdatachannelpy_src_dir, file),
262
327
  )
263
328
 
329
+ for file in os.listdir(os.path.join(libdatachannelpy_build_target_dir, "libyuv")):
330
+ if file in ("__init__.pyi",):
331
+ shutil.copyfile(
332
+ os.path.join(libdatachannelpy_build_target_dir, "libyuv", file),
333
+ os.path.join(libdatachannelpy_src_dir, "libyuv", file),
334
+ )
335
+
336
+ for file in os.listdir(os.path.join(libdatachannelpy_build_target_dir, "codec")):
337
+ if file in ("__init__.pyi",):
338
+ shutil.copyfile(
339
+ os.path.join(libdatachannelpy_build_target_dir, "codec", file),
340
+ os.path.join(libdatachannelpy_src_dir, "codec", file),
341
+ )
342
+
264
343
 
265
344
  if __name__ == "__main__":
266
345
  main()
@@ -1,9 +1,9 @@
1
1
  from collections.abc import Callable, Sequence
2
2
  import datetime
3
3
  import enum
4
- from typing import Annotated, overload
4
+ from typing import overload
5
5
 
6
- from numpy.typing import ArrayLike
6
+ from . import codec as codec, libyuv as libyuv
7
7
 
8
8
 
9
9
  class IceServer:
@@ -1667,211 +1667,3 @@ class WebSocketServer:
1667
1667
  def port(self) -> int: ...
1668
1668
 
1669
1669
  def on_client(self, callback: Callable[[WebSocket], None]) -> None: ...
1670
-
1671
- class VideoCodecType(enum.Enum):
1672
- H264 = 0
1673
-
1674
- H265 = 1
1675
-
1676
- VP8 = 2
1677
-
1678
- VP9 = 3
1679
-
1680
- AV1 = 4
1681
-
1682
- class ImageFormat(enum.Enum):
1683
- I420 = 0
1684
-
1685
- NV12 = 1
1686
-
1687
- BGR888 = 2
1688
-
1689
- class VideoFrameBufferI420:
1690
- @staticmethod
1691
- def create(arg0: int, arg1: int, /) -> VideoFrameBufferI420: ...
1692
-
1693
- @property
1694
- def y(self) -> Annotated[ArrayLike, dict(dtype='uint8', shape=(None, None), order='C')]: ...
1695
-
1696
- @y.setter
1697
- def y(self, arg: Annotated[ArrayLike, dict(dtype='uint8', shape=(None, None), order='C')], /) -> None: ...
1698
-
1699
- @property
1700
- def u(self) -> Annotated[ArrayLike, dict(dtype='uint8', shape=(None, None), order='C')]: ...
1701
-
1702
- @u.setter
1703
- def u(self, arg: Annotated[ArrayLike, dict(dtype='uint8', shape=(None, None), order='C')], /) -> None: ...
1704
-
1705
- @property
1706
- def v(self) -> Annotated[ArrayLike, dict(dtype='uint8', shape=(None, None), order='C')]: ...
1707
-
1708
- @v.setter
1709
- def v(self, arg: Annotated[ArrayLike, dict(dtype='uint8', shape=(None, None), order='C')], /) -> None: ...
1710
-
1711
- def width(self) -> int: ...
1712
-
1713
- def height(self) -> int: ...
1714
-
1715
- def stride_y(self) -> int: ...
1716
-
1717
- def stride_u(self) -> int: ...
1718
-
1719
- def stride_v(self) -> int: ...
1720
-
1721
- class VideoFrameBufferNV12:
1722
- @staticmethod
1723
- def create(arg0: int, arg1: int, /) -> VideoFrameBufferNV12: ...
1724
-
1725
- @property
1726
- def y(self) -> Annotated[ArrayLike, dict(dtype='uint8', shape=(None, None), order='C')]: ...
1727
-
1728
- @y.setter
1729
- def y(self, arg: Annotated[ArrayLike, dict(dtype='uint8', shape=(None, None), order='C')], /) -> None: ...
1730
-
1731
- @property
1732
- def uv(self) -> Annotated[ArrayLike, dict(dtype='uint8', shape=(None, None), order='C')]: ...
1733
-
1734
- @uv.setter
1735
- def uv(self, arg: Annotated[ArrayLike, dict(dtype='uint8', shape=(None, None), order='C')], /) -> None: ...
1736
-
1737
- def width(self) -> int: ...
1738
-
1739
- def height(self) -> int: ...
1740
-
1741
- def stride_y(self) -> int: ...
1742
-
1743
- def stride_uv(self) -> int: ...
1744
-
1745
- class VideoFrameBufferBGR888:
1746
- @staticmethod
1747
- def create(arg0: int, arg1: int, /) -> VideoFrameBufferBGR888: ...
1748
-
1749
- @property
1750
- def bgr(self) -> Annotated[ArrayLike, dict(dtype='uint8', shape=(None, None, 3), order='C')]: ...
1751
-
1752
- @bgr.setter
1753
- def bgr(self, arg: Annotated[ArrayLike, dict(dtype='uint8', shape=(None, None, 3), order='C')], /) -> None: ...
1754
-
1755
- def width(self) -> int: ...
1756
-
1757
- def height(self) -> int: ...
1758
-
1759
- def stride(self) -> int: ...
1760
-
1761
- class VideoFrame:
1762
- def __init__(self) -> None: ...
1763
-
1764
- @property
1765
- def format(self) -> ImageFormat: ...
1766
-
1767
- @format.setter
1768
- def format(self, arg: ImageFormat, /) -> None: ...
1769
-
1770
- @property
1771
- def i420_buffer(self) -> VideoFrameBufferI420: ...
1772
-
1773
- @i420_buffer.setter
1774
- def i420_buffer(self, arg: VideoFrameBufferI420, /) -> None: ...
1775
-
1776
- @property
1777
- def nv12_buffer(self) -> VideoFrameBufferNV12: ...
1778
-
1779
- @nv12_buffer.setter
1780
- def nv12_buffer(self, arg: VideoFrameBufferNV12, /) -> None: ...
1781
-
1782
- @property
1783
- def bgr888_buffer(self) -> VideoFrameBufferBGR888: ...
1784
-
1785
- @bgr888_buffer.setter
1786
- def bgr888_buffer(self, arg: VideoFrameBufferBGR888, /) -> None: ...
1787
-
1788
- @property
1789
- def timestamp(self) -> datetime.timedelta: ...
1790
-
1791
- @timestamp.setter
1792
- def timestamp(self, arg: datetime.timedelta | float, /) -> None: ...
1793
-
1794
- @property
1795
- def rid(self) -> str | None: ...
1796
-
1797
- @rid.setter
1798
- def rid(self, arg: str, /) -> None: ...
1799
-
1800
- @property
1801
- def base_width(self) -> int: ...
1802
-
1803
- @base_width.setter
1804
- def base_width(self, arg: int, /) -> None: ...
1805
-
1806
- @property
1807
- def base_height(self) -> int: ...
1808
-
1809
- @base_height.setter
1810
- def base_height(self, arg: int, /) -> None: ...
1811
-
1812
- def width(self) -> int: ...
1813
-
1814
- def height(self) -> int: ...
1815
-
1816
- class EncodedImage:
1817
- def __init__(self) -> None: ...
1818
-
1819
- @property
1820
- def data(self) -> Annotated[ArrayLike, dict(dtype='uint8', shape=(None), order='C')]: ...
1821
-
1822
- @data.setter
1823
- def data(self, arg: Annotated[ArrayLike, dict(dtype='uint8', shape=(None), order='C')], /) -> None: ...
1824
-
1825
- @property
1826
- def timestamp(self) -> datetime.timedelta: ...
1827
-
1828
- @timestamp.setter
1829
- def timestamp(self, arg: datetime.timedelta | float, /) -> None: ...
1830
-
1831
- @property
1832
- def rid(self) -> str | None: ...
1833
-
1834
- @rid.setter
1835
- def rid(self, arg: str, /) -> None: ...
1836
-
1837
- class VideoEncoder:
1838
- def init(self, arg: VideoEncoder.Settings, /) -> bool: ...
1839
-
1840
- def release(self) -> None: ...
1841
-
1842
- def encode(self, arg: VideoFrame, /) -> None: ...
1843
-
1844
- def force_intra_next_frame(self) -> None: ...
1845
-
1846
- def set_on_encoded(self, arg: Callable[[EncodedImage], None], /) -> None: ...
1847
-
1848
- class Settings:
1849
- def __init__(self) -> None: ...
1850
-
1851
- @property
1852
- def codec_type(self) -> VideoCodecType: ...
1853
-
1854
- @codec_type.setter
1855
- def codec_type(self, arg: VideoCodecType, /) -> None: ...
1856
-
1857
- @property
1858
- def width(self) -> int: ...
1859
-
1860
- @width.setter
1861
- def width(self, arg: int, /) -> None: ...
1862
-
1863
- @property
1864
- def height(self) -> int: ...
1865
-
1866
- @height.setter
1867
- def height(self, arg: int, /) -> None: ...
1868
-
1869
- @property
1870
- def bitrate(self) -> int: ...
1871
-
1872
- @bitrate.setter
1873
- def bitrate(self, arg: int, /) -> None: ...
1874
-
1875
- def create_openh264_video_encoder(openh264: str) -> VideoEncoder: ...
1876
-
1877
- def create_videotoolbox_video_encoder() -> VideoEncoder: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: libdatachannel-py
3
- Version: 2025.1.0.dev6
3
+ Version: 2025.1.0.dev8
4
4
  Summary: libdatachannel-py is a Python binding for libdatachannel.
5
5
  Home-page: https://github.com/shiguredo/libdatachannel-py
6
6
  License-Expression: Apache-2.0
@@ -15,11 +15,13 @@ src/libdatachannel_py.egg-info/PKG-INFO
15
15
  src/libdatachannel_py.egg-info/SOURCES.txt
16
16
  src/libdatachannel_py.egg-info/dependency_links.txt
17
17
  src/libdatachannel_py.egg-info/top_level.txt
18
+ tests/test_audio_codec.py
18
19
  tests/test_candidate.py
19
20
  tests/test_configuration.py
20
21
  tests/test_description.py
21
22
  tests/test_frameinfo.py
22
23
  tests/test_h265nalunit.py
24
+ tests/test_libyuv.py
23
25
  tests/test_mediahandler.py
24
26
  tests/test_message.py
25
27
  tests/test_nalunit.py
@@ -0,0 +1,72 @@
1
+ from datetime import timedelta
2
+
3
+ import numpy as np
4
+
5
+ from libdatachannel.codec import (
6
+ AudioCodecType,
7
+ AudioEncoder,
8
+ AudioFrame,
9
+ EncodedAudio,
10
+ create_opus_audio_encoder,
11
+ )
12
+
13
+
14
+ def test_audio_encoder_encode():
15
+ encoder = create_opus_audio_encoder()
16
+
17
+ settings = AudioEncoder.Settings()
18
+ settings.codec_type = AudioCodecType.OPUS
19
+ settings.sample_rate = 48000
20
+ settings.channels = 2
21
+ settings.bitrate = 64000
22
+ settings.frame_duration_ms = 20
23
+
24
+ assert encoder.init(settings) is True
25
+
26
+ callback_timestamps = []
27
+
28
+ def on_encoded(encoded: EncodedAudio):
29
+ assert isinstance(encoded.data, np.ndarray)
30
+ callback_timestamps.append(encoded.timestamp)
31
+
32
+ encoder.set_on_encode(on_encoded)
33
+
34
+ frame = AudioFrame()
35
+ frame.sample_rate = 48000
36
+ frame.timestamp = timedelta(milliseconds=1000)
37
+ # 48000 Hz * 20 ms = 960 samples
38
+ frame.pcm = np.zeros((960, 2), dtype=np.float32)
39
+
40
+ encoder.encode(frame)
41
+
42
+ assert callback_timestamps == [timedelta(milliseconds=1000)]
43
+
44
+ # 10ms → 20ms でエンコード
45
+ frame = AudioFrame()
46
+ frame.sample_rate = 48000
47
+ frame.timestamp = timedelta(milliseconds=1000)
48
+ frame.pcm = np.zeros((480, 2), dtype=np.float32)
49
+
50
+ encoder.encode(frame)
51
+
52
+ frame.timestamp = timedelta(milliseconds=1100) # わざと大きくずらす
53
+ frame.pcm = np.zeros((960, 2), dtype=np.float32)
54
+
55
+ encoder.encode(frame)
56
+
57
+ # 既存のフレームが 10ms あって、1100 だったので 1100 - 10 の 1090 になるはず
58
+ assert callback_timestamps == [timedelta(milliseconds=1000), timedelta(milliseconds=1090)]
59
+
60
+ # 10ms のデータを渡すと追加でエンコードされるはず
61
+ frame.timestamp = timedelta(milliseconds=1120)
62
+ frame.pcm = np.zeros((480, 2), dtype=np.float32)
63
+
64
+ encoder.encode(frame)
65
+
66
+ assert callback_timestamps == [
67
+ timedelta(milliseconds=1000),
68
+ timedelta(milliseconds=1090),
69
+ timedelta(milliseconds=1110),
70
+ ]
71
+
72
+ encoder.release()
@@ -0,0 +1,200 @@
1
+ import numpy as np
2
+
3
+ from libdatachannel.codec import VideoFrameBufferBGR888, VideoFrameBufferI420, VideoFrameBufferNV12
4
+ from libdatachannel.libyuv import (
5
+ FilterMode,
6
+ FourCC,
7
+ RotationMode,
8
+ convert_to_i420,
9
+ i420_scale,
10
+ i420_to_nv12,
11
+ i420_to_rgb24,
12
+ nv12_scale,
13
+ nv12_to_i420,
14
+ rgb24_to_i420,
15
+ )
16
+
17
+
18
+ def test_libyuv_nv12_scale():
19
+ src_width, src_height = 640, 480
20
+ src = VideoFrameBufferNV12.create(src_width, src_height)
21
+ # type: ignore しないと型エラーになる
22
+ # 参考: https://github.com/wjakob/nanobind/pull/442
23
+ src.y[:, :] = 42 # type: ignore
24
+
25
+ dst_width, dst_height = 320, 240
26
+ dst = VideoFrameBufferNV12.create(dst_width, dst_height)
27
+ r = nv12_scale(
28
+ src.y,
29
+ src.uv,
30
+ src.stride_y(),
31
+ src.stride_uv(),
32
+ src.width(),
33
+ src.height(),
34
+ dst.y,
35
+ dst.uv,
36
+ dst.stride_y(),
37
+ dst.stride_uv(),
38
+ dst.width(),
39
+ dst.height(),
40
+ FilterMode.NONE,
41
+ )
42
+
43
+ assert r == 0
44
+ assert np.all(dst.y == 42)
45
+
46
+
47
+ def test_libyuv_i420_scale():
48
+ src_width, src_height = 640, 480
49
+ src = VideoFrameBufferI420.create(src_width, src_height)
50
+ src.y[:, :] = 42 # type: ignore
51
+
52
+ dst_width, dst_height = 320, 240
53
+ dst = VideoFrameBufferI420.create(dst_width, dst_height)
54
+ r = i420_scale(
55
+ src.y,
56
+ src.u,
57
+ src.v,
58
+ src.stride_y(),
59
+ src.stride_u(),
60
+ src.stride_v(),
61
+ src.width(),
62
+ src.height(),
63
+ dst.y,
64
+ dst.u,
65
+ dst.v,
66
+ dst.stride_y(),
67
+ dst.stride_u(),
68
+ dst.stride_v(),
69
+ dst.width(),
70
+ dst.height(),
71
+ FilterMode.NONE,
72
+ )
73
+ assert r == 0
74
+ assert np.all(dst.y == 42)
75
+
76
+
77
+ def test_libyuv_convert_to_i420():
78
+ nv12 = np.full((640, 480 + 480 // 2), 42, dtype=np.uint8)
79
+
80
+ dst = VideoFrameBufferI420.create(640, 480)
81
+ r = convert_to_i420(
82
+ nv12,
83
+ nv12.size,
84
+ dst.y,
85
+ dst.stride_y(),
86
+ dst.u,
87
+ dst.stride_u(),
88
+ dst.v,
89
+ dst.stride_v(),
90
+ 0,
91
+ 0,
92
+ 640,
93
+ 480,
94
+ 640,
95
+ 480,
96
+ RotationMode.Rotate0,
97
+ FourCC.NV12,
98
+ )
99
+ assert r == 0
100
+ assert np.all(dst.y == 42)
101
+
102
+
103
+ def test_libyuv_nv12_to_i420():
104
+ src = VideoFrameBufferNV12.create(640, 480)
105
+ src.y[:, :] = 42 # type: ignore
106
+ src.uv[:, :] = 128 # type: ignore
107
+
108
+ dst = VideoFrameBufferI420.create(640, 480)
109
+ r = nv12_to_i420(
110
+ src.y,
111
+ src.uv,
112
+ src.stride_y(),
113
+ src.stride_uv(),
114
+ dst.y,
115
+ dst.u,
116
+ dst.v,
117
+ dst.stride_y(),
118
+ dst.stride_u(),
119
+ dst.stride_v(),
120
+ src.width(),
121
+ src.height(),
122
+ )
123
+
124
+ assert r == 0
125
+ assert np.all(dst.y == 42)
126
+ assert np.all(dst.u == 128)
127
+ assert np.all(dst.v == 128)
128
+
129
+
130
+ def test_libyuv_i420_to_nv12():
131
+ src = VideoFrameBufferI420.create(640, 480)
132
+ src.y[:, :] = 42 # type: ignore
133
+ src.u[:, :] = 128 # type: ignore
134
+ src.v[:, :] = 128 # type: ignore
135
+
136
+ dst = VideoFrameBufferNV12.create(640, 480)
137
+ r = i420_to_nv12(
138
+ src.y,
139
+ src.u,
140
+ src.v,
141
+ src.stride_y(),
142
+ src.stride_u(),
143
+ src.stride_v(),
144
+ dst.y,
145
+ dst.uv,
146
+ dst.stride_y(),
147
+ dst.stride_uv(),
148
+ src.width(),
149
+ src.height(),
150
+ )
151
+
152
+ assert r == 0
153
+ assert np.all(dst.y == 42)
154
+ assert np.all(dst.uv == 128)
155
+
156
+
157
+ def test_libyuv_rgb24_to_i420():
158
+ src = VideoFrameBufferBGR888.create(640, 480)
159
+ src.bgr[:, :, :] = 42 # type: ignore
160
+
161
+ dst = VideoFrameBufferI420.create(640, 480)
162
+ r = rgb24_to_i420(
163
+ src.bgr,
164
+ src.stride(),
165
+ dst.y,
166
+ dst.u,
167
+ dst.v,
168
+ dst.stride_y(),
169
+ dst.stride_u(),
170
+ dst.stride_v(),
171
+ src.width(),
172
+ src.height(),
173
+ )
174
+
175
+ assert r == 0
176
+ assert np.all(dst.y == 52)
177
+
178
+
179
+ def test_libyuv_i420_to_rgb24():
180
+ src = VideoFrameBufferI420.create(640, 480)
181
+ src.y[:, :] = 42 # type: ignore
182
+ src.u[:, :] = 128 # type: ignore
183
+ src.v[:, :] = 128 # type: ignore
184
+
185
+ dst = VideoFrameBufferBGR888.create(640, 480)
186
+ r = i420_to_rgb24(
187
+ src.y,
188
+ src.u,
189
+ src.v,
190
+ src.stride_y(),
191
+ src.stride_u(),
192
+ src.stride_v(),
193
+ dst.bgr,
194
+ dst.stride(),
195
+ src.width(),
196
+ src.height(),
197
+ )
198
+
199
+ assert r == 0
200
+ assert np.all(dst.bgr == np.array([30, 30, 30], dtype=np.uint8))
@@ -5,7 +5,7 @@ from datetime import timedelta
5
5
  import numpy as np
6
6
  import pytest
7
7
 
8
- from libdatachannel import (
8
+ from libdatachannel.codec import (
9
9
  EncodedImage,
10
10
  ImageFormat,
11
11
  VideoCodecType,
@@ -14,6 +14,7 @@ from libdatachannel import (
14
14
  VideoFrameBufferBGR888,
15
15
  VideoFrameBufferI420,
16
16
  VideoFrameBufferNV12,
17
+ create_aom_video_encoder,
17
18
  create_openh264_video_encoder,
18
19
  create_videotoolbox_video_encoder,
19
20
  )
@@ -120,19 +121,19 @@ def test_openh264():
120
121
  frame.base_height = 480
121
122
  frame.timestamp = timedelta(microseconds=1234567)
122
123
 
123
- on_encoded_called = False
124
+ on_encode_called = False
124
125
 
125
- def on_encoded(encoded_image):
126
- nonlocal on_encoded_called
126
+ def on_encode(encoded_image):
127
+ nonlocal on_encode_called
127
128
  assert encoded_image.data.size > 0
128
129
  assert encoded_image.timestamp == frame.timestamp
129
130
  assert encoded_image.rid == frame.rid
130
- on_encoded_called = True
131
+ on_encode_called = True
131
132
 
132
- encoder.set_on_encoded(on_encoded)
133
+ encoder.set_on_encode(on_encode)
133
134
  encoder.encode(frame)
134
135
  encoder.release()
135
- assert on_encoded_called
136
+ assert on_encode_called
136
137
 
137
138
 
138
139
  @pytest.mark.skipif(
@@ -154,17 +155,48 @@ def test_videotoolbox():
154
155
  frame.base_height = 480
155
156
  frame.timestamp = timedelta(microseconds=1234567)
156
157
 
157
- on_encoded_called = False
158
+ on_encode_called = False
158
159
 
159
- def on_encoded(encoded_image):
160
- nonlocal on_encoded_called
160
+ def on_encode(encoded_image):
161
+ nonlocal on_encode_called
161
162
  assert encoded_image.data.size > 0
162
163
  assert encoded_image.timestamp == frame.timestamp
163
164
  assert encoded_image.rid == frame.rid
164
- on_encoded_called = True
165
+ on_encode_called = True
165
166
 
166
- encoder.set_on_encoded(on_encoded)
167
+ encoder.set_on_encode(on_encode)
167
168
  encoder.encode(frame)
168
169
  time.sleep(1)
169
170
  encoder.release()
170
- assert on_encoded_called
171
+ assert on_encode_called
172
+
173
+
174
+ def test_aom():
175
+ encoder = create_aom_video_encoder()
176
+ settings = VideoEncoder.Settings()
177
+ settings.codec_type = VideoCodecType.H264
178
+ settings.width = 640
179
+ settings.height = 480
180
+ settings.bitrate = 1000000
181
+ success = encoder.init(settings)
182
+ assert success
183
+ frame = VideoFrame()
184
+ frame.i420_buffer = VideoFrameBufferI420.create(640, 360)
185
+ frame.format = ImageFormat.I420
186
+ frame.base_width = 640
187
+ frame.base_height = 480
188
+ frame.timestamp = timedelta(microseconds=1234567)
189
+
190
+ on_encode_called = False
191
+
192
+ def on_encode(encoded_image):
193
+ nonlocal on_encode_called
194
+ assert encoded_image.data.size > 0
195
+ assert encoded_image.timestamp == frame.timestamp
196
+ assert encoded_image.rid == frame.rid
197
+ on_encode_called = True
198
+
199
+ encoder.set_on_encode(on_encode)
200
+ encoder.encode(frame)
201
+ encoder.release()
202
+ assert on_encode_called
@@ -1 +0,0 @@
1
- 2025.1.0.dev6