agora-python-server-sdk 2.3.3__tar.gz → 2.4.0__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 (48) hide show
  1. {agora_python_server_sdk-2.3.3/agora_python_server_sdk.egg-info → agora_python_server_sdk-2.4.0}/PKG-INFO +20 -1
  2. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/README.md +19 -0
  3. agora_python_server_sdk-2.4.0/agora/__init__.py +211 -0
  4. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/__init__.py +7 -1
  5. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/_ctypes_handle/_local_user_observer.py +2 -0
  6. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/agora_base.py +63 -1
  7. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/agora_service.py +53 -1
  8. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/local_user.py +4 -0
  9. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/rtc_connection.py +27 -1
  10. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/utils/vad_dump.py +78 -0
  11. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/voice_detection.py +55 -21
  12. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtm/__init__.py +1 -0
  13. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0/agora_python_server_sdk.egg-info}/PKG-INFO +20 -1
  14. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora_python_server_sdk.egg-info/SOURCES.txt +1 -0
  15. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/setup.py +8 -2
  16. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/MANIFEST.in +0 -0
  17. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/_ctypes_handle/_audio_frame_observer.py +0 -0
  18. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/_ctypes_handle/_ctypes_data.py +0 -0
  19. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/_ctypes_handle/_rtc_connection_observer.py +0 -0
  20. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/_ctypes_handle/_video_encoded_frame_observer.py +0 -0
  21. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/_ctypes_handle/_video_frame_observer.py +0 -0
  22. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/_utils/globals.py +0 -0
  23. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/agora_parameter.py +0 -0
  24. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/audio_encoded_frame_sender.py +0 -0
  25. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/audio_frame_observer.py +0 -0
  26. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/audio_pcm_data_sender.py +0 -0
  27. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/audio_sessionctrl.py +0 -0
  28. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/audio_vad_manager.py +0 -0
  29. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/local_audio_track.py +0 -0
  30. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/local_user_observer.py +0 -0
  31. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/local_video_track.py +0 -0
  32. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/media_node_factory.py +0 -0
  33. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/remote_audio_track.py +0 -0
  34. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/remote_video_track.py +0 -0
  35. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/rtc_connection_observer.py +0 -0
  36. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/utils/audio_consumer.py +0 -0
  37. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/video_encoded_frame_observer.py +0 -0
  38. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/video_encoded_image_sender.py +0 -0
  39. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/video_frame_observer.py +0 -0
  40. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtc/video_frame_sender.py +0 -0
  41. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtm/_ctypes_handle/_ctypes_data.py +0 -0
  42. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtm/rtm_base.py +0 -0
  43. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtm/rtm_client.py +0 -0
  44. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora/rtm/rtm_event_handler.py +0 -0
  45. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora_python_server_sdk.egg-info/dependency_links.txt +0 -0
  46. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/agora_python_server_sdk.egg-info/top_level.txt +0 -0
  47. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/pyproject.toml +0 -0
  48. {agora_python_server_sdk-2.3.3 → agora_python_server_sdk-2.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: agora_python_server_sdk
3
- Version: 2.3.3
3
+ Version: 2.4.0
4
4
  Summary: A Python SDK for Agora Server
5
5
  Home-page: https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK
6
6
  Classifier: Intended Audience :: Developers
@@ -64,6 +64,25 @@ python agora_rtc/examples/example_audio_pcm_send.py --appId=xxx --channelId=xxx
64
64
  ```
65
65
 
66
66
  # Change log
67
+ ## 2025.11.18 Release version 2.4.0
68
+ -- Update SDK to APM version: 4.4.32/1025
69
+ -- Add configure support for enabling/disabling APM
70
+ -- Update methods in setup.py and __init__.py, ok, including version/URL, md5, etc.
71
+ -- Overall code pipeline ok, needs testing?? ok
72
+ -- Add support to rtm, one single sdk supports both rtm and rtc, ok
73
+ -- todo:
74
+ -[] Need to add VAD algorithm update ok
75
+ -[] Need to add vad_dump modifications ok
76
+ -[] Modify APM algorithm to support VAD switch, ok
77
+ -[] Add VAD configure parameter settings, ok
78
+ -[] Download every time, check md5 mismatch?? ok
79
+ NOTE:
80
+ APM features, i.e., server-side echo cancellation, noise suppression, automatic gain control, background voice removal, etc.
81
+ Normally, AEC/AINS/AGC, etc., are already implemented on the client side, and the server side does not need to implement them again, unless there are special requirements.
82
+ If you want to enable APM features, please contact Agora technical support.
83
+ NOTE:
84
+ How to use rtc,please ref to: https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK/tree/main/agora_rtc/examples
85
+ Hot to use rtm,please ref to ://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK/tree/main/agora_rtc/rtm_examples
67
86
  # 2025.11.07 release 2.3.3
68
87
  -- update: to support rtm
69
88
  -- adjust sdk's directory structure
@@ -49,6 +49,25 @@ python agora_rtc/examples/example_audio_pcm_send.py --appId=xxx --channelId=xxx
49
49
  ```
50
50
 
51
51
  # Change log
52
+ ## 2025.11.18 Release version 2.4.0
53
+ -- Update SDK to APM version: 4.4.32/1025
54
+ -- Add configure support for enabling/disabling APM
55
+ -- Update methods in setup.py and __init__.py, ok, including version/URL, md5, etc.
56
+ -- Overall code pipeline ok, needs testing?? ok
57
+ -- Add support to rtm, one single sdk supports both rtm and rtc, ok
58
+ -- todo:
59
+ -[] Need to add VAD algorithm update ok
60
+ -[] Need to add vad_dump modifications ok
61
+ -[] Modify APM algorithm to support VAD switch, ok
62
+ -[] Add VAD configure parameter settings, ok
63
+ -[] Download every time, check md5 mismatch?? ok
64
+ NOTE:
65
+ APM features, i.e., server-side echo cancellation, noise suppression, automatic gain control, background voice removal, etc.
66
+ Normally, AEC/AINS/AGC, etc., are already implemented on the client side, and the server side does not need to implement them again, unless there are special requirements.
67
+ If you want to enable APM features, please contact Agora technical support.
68
+ NOTE:
69
+ How to use rtc,please ref to: https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK/tree/main/agora_rtc/examples
70
+ Hot to use rtm,please ref to ://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK/tree/main/agora_rtc/rtm_examples
52
71
  # 2025.11.07 release 2.3.3
53
72
  -- update: to support rtm
54
73
  -- adjust sdk's directory structure
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env python
2
+
3
+ import hashlib
4
+ import ssl
5
+ import zipfile
6
+ import site
7
+ from urllib import request
8
+ import ctypes
9
+ import os
10
+ import sys
11
+ import platform
12
+ import logging
13
+ logger = logging.getLogger(__name__)
14
+ ssl._create_default_https_context = ssl._create_unverified_context
15
+
16
+ '''
17
+ # this is the global init for agora python server sdk, include rtc and rtm
18
+ # it will check the sdk version and download the latest sdk if needed
19
+ path dir structure is:
20
+ /home/xxx/agora/
21
+ agora_sdk
22
+ include
23
+ *.so/*.dylib
24
+ rtc
25
+ __init__.py
26
+ rtc*.py
27
+ rtm
28
+ __init__.py
29
+ rtm*.py
30
+ __init__.py
31
+ setup.py
32
+ README.md
33
+ LICENSE
34
+ CHANGELOG.md
35
+ CONTRIBUTING.md
36
+ CODE_OF_CONDUCT.md
37
+ SECURITY.md
38
+ CONTRIBUTORS.md
39
+ '''
40
+ '''
41
+ # requirement for package zip file:
42
+ no root dir, only so/dylib file in the root dir
43
+ '''
44
+
45
+
46
+
47
+
48
+ def get_file_md5(file_path):
49
+ hash_md5 = hashlib.md5()
50
+ try:
51
+ with open(file_path, "rb") as f:
52
+ for chunk in iter(lambda: f.read(4096), b""):
53
+ hash_md5.update(chunk)
54
+ return hash_md5.hexdigest()
55
+ except Exception as e:
56
+ logger.error(f"get_file_md5 error: {e}")
57
+ return ""
58
+
59
+
60
+ #get agora root path, like: /home/xxx/agora
61
+ def get_sdk_root_path():
62
+ agora_path = os.path.dirname(os.path.abspath(__file__))
63
+ return agora_path
64
+ #get agora 's library path, like: /home/xxx/agora/agora_sdk/
65
+ def get_sdk_library_path():
66
+ agora_path = get_sdk_root_path()
67
+ library_path = os.path.join(agora_path, "agora_sdk")
68
+
69
+ return library_path
70
+ def get_sdk_rtc_path():
71
+ agora_path = get_sdk_root_path()
72
+ rtc_path = os.path.join(agora_path, "rtc")
73
+ return rtc_path
74
+ def get_sdk_rtm_path():
75
+ agora_path = get_sdk_root_path()
76
+ rtm_path = os.path.join(agora_path, "rtm")
77
+ return rtm_path
78
+
79
+
80
+ #helper function to download file from ur
81
+ def report_progress(blocknum, blocksize, totalsize):
82
+ """
83
+ 下载进度回调函数
84
+ """
85
+ if totalsize > 0:
86
+ # calculate download progress percentage
87
+ percent = min(100, (blocknum * blocksize) / totalsize * 100)
88
+ # use carriage return to overwrite current line, to update progress in place
89
+ print(f"\rDownloading: ----{percent:.2f}%-----", end='', flush=True)
90
+ # when download is complete (or calculated value exceeds 100%)
91
+ if percent >= 100:
92
+ print("Downloading: ----100.00%-----\n")
93
+ else:
94
+ # if cannot get file total size, show downloaded bytes
95
+ downloaded = blocknum * blocksize
96
+ print(f"\rDownloading: ----{downloaded} bytes-----", end='', flush=True)
97
+
98
+
99
+ def _check_download_and_extract_sdk():
100
+ agora_service_path = os.path.dirname(os.path.abspath(__file__))
101
+ # change from dir like: /home/xxx/agora_rtc/agora/rtc/agora_sdk to
102
+ # /home/xxx/agora_rtc/agora/agora_sdk
103
+ # /home/xxx/agora_rtc/agora/rtc
104
+ # /home/xxx/agora_rtc/agora/rtm
105
+ global sdk_library_dir, sdk_root_dir
106
+
107
+ zip_path = os.path.join(sdk_root_dir, "agora_rtc_sdk.zip")
108
+ logger.error(f"sdk_library_dir: {sdk_library_dir}")
109
+ logger.error(f"zip_path: {zip_path}")
110
+
111
+ # for diff os and arch
112
+ arch = platform.machine()
113
+ os_type = platform.system()
114
+
115
+
116
+
117
+ #url = "https://download.agora.io/sdk/release/agora_rtc_sdk-x86_64-linux-gnu-v4.4.30-20241024_101940-398537.zip"
118
+ # version 2.2.0 for linux
119
+ #url = "https://download.agora.io/sdk/release/agora_rtc_sdk-x86_64-linux-gnu-v4.4.31-20241223_111509-491956.zip"
120
+ #url = "https://download.agora.io/sdk/release/agora_rtc_sdk-x86_64-linux-gnu-v4.4.32-20250715_161625-791246.zip"
121
+ #url = "https://download.agora.io/sdk/release/agora_rtc_sdk-x86_64-linux-gnu-v4.4.32-20250829_160340-860733.zip"
122
+ #fusion version: 20251023
123
+
124
+ url = "https://download.agora.io/sdk/release/agora_rtc_sdk-x86_64-linux-gnu-v4.4.32-20250829_160340-860733-aed_20251107_1642.zip"
125
+ #20251110 Fusion version: with apm filter
126
+ mac_sdk="https://download.agora.io/sdk/release/agora_sdk_mac_v4.4.30_25869_FULL_20251030_1836_953684-aed.zip"
127
+ linux_sdk = "https://download.agora.io/sdk/release/agora_rtc_sdk_x86_64-linux-gnu-v4.4.32.150_26715_SERVER_20251030_1807-aed.zip"
128
+
129
+
130
+ linux_libfile_path = os.path.join(sdk_library_dir, "libagora_rtc_sdk.so")
131
+ mac_libfile_path = os.path.join(sdk_library_dir, "libAgoraRtcKit.dylib")
132
+ linux_md5 = "821cb1a388279648fcb204ca795e6476"
133
+ mac_md5 = "5b9940d3fca033a53ac30216d5c39be6"
134
+
135
+ #rtc_md5 = "7031dd10d1681cd88fd89d68c5b54282"
136
+ url = linux_sdk
137
+ rtc_md5 = linux_md5
138
+ rtc_libfile_path = linux_libfile_path
139
+ if sys.platform == 'darwin':
140
+ #url = "https://download.agora.io/sdk/release/agora_rtc_sdk_mac_rel.v4.4.30_22472_FULL_20241024_1224_398653.zip"
141
+ # version 2.2.0 for mac
142
+ #url = "https://download.agora.io/sdk/release/agora_sdk_mac_v4.4.31_23136_FULL_20241223_1245_492039.zip"
143
+ #url = "https://download.agora.io/sdk/release/agora_sdk_mac_v4.4.32_24915_FULL_20250715_1710_791284.zip"
144
+ #url = "https://download.agora.io/sdk/release/agora_sdk_mac_v4.4.32_25418_FULL_20250829_1647_860754.zip"
145
+ #20251023 Fusion version: one sdk package include rtc and rtm
146
+ #url = "https://download.agora.io/sdk/release/agora_sdk_mac_v4.4.32_25418_FULL_20250829_1647_860754-aed_20251107_1639.zip"
147
+ url = mac_sdk
148
+
149
+ rtc_libfile_path = mac_libfile_path
150
+ #rtc_md5 = "ca3ca14f9e2b7d97eb2594d1f32dab9f"
151
+ rtc_md5 = mac_md5
152
+ if arch == "aarch64" and sys.platform == 'linux':
153
+ #url = "https://download.agora.io/sdk/release/Agora-RTC-aarch64-linux-gnu-v4.4.31-20250307_175457-603878.zip"
154
+ #url = "https://download.agora.io/sdk/release/Agora-RTC-aarch64-linux-gnu-v4.4.32-20250425_150503-675674.zip"
155
+ #url = "https://download.agora.io/sdk/release/Agora-RTC-aarch64-linux-gnu-v4.4.32-20251009_145437-921455.zip"
156
+ url = "https://download.agora.io/sdk/release/Agora-RTC-aarch64-linux-gnu-v4.4.32-20251009_145437-921455_20251023_1538.zip"
157
+ rtc_md5 = "5c002f25d2b381e353082da4f835b4f2"
158
+
159
+ is_file_exist = os.path.exists(rtc_libfile_path)
160
+ if is_file_exist:
161
+ md5_value = get_file_md5(rtc_libfile_path)
162
+ else:
163
+ md5_value = ""
164
+ if md5_value == rtc_md5:
165
+ return
166
+
167
+ logger.error(f"missing agora sdk, now download it, please wait for a while...: {rtc_libfile_path} {md5_value} {rtc_md5} {is_file_exist}")
168
+ if os.path.exists(sdk_library_dir):
169
+ os.system(f"rm -rf {sdk_library_dir}")
170
+ os.makedirs(sdk_library_dir, exist_ok=True)
171
+ if os.path.exists(zip_path):
172
+ os.remove(zip_path)
173
+
174
+ logger.error(f"sdk_library_dir: {sdk_library_dir}")
175
+ logger.error(f"Downloading {url}...")
176
+ #download_file_with_progress(url, zip_path)
177
+ request.urlretrieve(url, zip_path,reporthook=report_progress)
178
+
179
+
180
+ logger.error(f"Extracting {zip_path}...")
181
+
182
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
183
+ zip_ref.extractall(sdk_root_dir)
184
+
185
+ if os.path.exists(zip_path):
186
+ os.remove(zip_path)
187
+ logger.error("download done, continue...")
188
+
189
+
190
+
191
+ sdk_root_dir = get_sdk_root_path()
192
+ sdk_rtc_dir = get_sdk_rtc_path()
193
+ sdk_rtm_dir = get_sdk_rtm_path()
194
+ sdk_library_dir = get_sdk_library_path()
195
+
196
+ _check_download_and_extract_sdk()
197
+
198
+
199
+ if sys.platform == 'darwin':
200
+ rtc_libfile_path = os.path.join(sdk_library_dir, 'libAgoraRtcKit.dylib')
201
+ else:
202
+ rtc_libfile_path = os.path.join(sdk_library_dir, 'libagora_rtc_sdk.so')
203
+ ctypes.CDLL(os.path.join(sdk_library_dir, 'libaosl.so'))
204
+
205
+ #check if the library exists
206
+ if not os.path.exists(rtc_libfile_path):
207
+ logger.error(f"library {rtc_libfile_path} not found")
208
+ sys.exit(1)
209
+
210
+ # 显式导出这些变量,确保子模块可以导入
211
+ __all__ = ['sdk_library_dir', 'sdk_rtc_dir', 'sdk_rtm_dir', 'sdk_root_dir']
@@ -22,11 +22,17 @@ try:
22
22
  if sys.platform == 'darwin':
23
23
  lib_agora_rtc_path = os.path.join(lib_dir, 'libAgoraRtcKit.dylib')
24
24
  agora_lib = ctypes.CDLL(lib_agora_rtc_path)
25
+ ctypes.CDLL(os.path.join(lib_dir, 'libAgoraAiNoiseSuppressionExtension.dylib'))
25
26
 
26
27
  elif sys.platform == 'linux':
27
28
  lib_agora_rtc_path = os.path.join(lib_dir, 'libagora_rtc_sdk.so')
28
- #ctypes.CDLL(os.path.join(lib_dir, 'libagora-fdkaac.so'))
29
+ ctypes.CDLL(os.path.join(lib_dir, 'libagora-fdkaac.so'))
30
+ #ctypes.CDLL(os.path.join(lib_dir, 'libagora_ai_noise_suppression_extension.so'))
31
+ ctypes.CDLL(os.path.join(lib_dir, 'libagora-ffmpeg.so'))
32
+ ctypes.CDLL(os.path.join(lib_dir, 'libagora-soundtouch.so'))
29
33
  agora_lib = ctypes.CDLL(lib_agora_rtc_path)
34
+ # should load it or the ains can not work
35
+ ctypes.CDLL(os.path.join(lib_dir, 'libagora_ai_noise_suppression_extension.so'))
30
36
  except OSError as e:
31
37
  logger.error(f"Error loading the library: {e}")
32
38
  logger.error(f"Attempted to load from: {lib_agora_rtc_path}")
@@ -186,6 +186,8 @@ class RTCLocalUserObserverInner(ctypes.Structure):
186
186
  def _on_user_audio_track_subscribed(self, local_user_handle, user_id, remote_audio_track_handle):
187
187
  logger.debug(f"LocalUserCB _on_user_audio_track_subscribed: {local_user_handle}, {user_id}, {remote_audio_track_handle}")
188
188
  user_id_str = user_id.decode('utf-8') if user_id else ""
189
+ #add apm filter for remote audio track
190
+ self.local_user._set_apm_filter_properties(remote_audio_track_handle,user_id_str)
189
191
  # note: this is a pointer to agora::rtc::IRemoteAudioTrack
190
192
  remote_audio_track = RemoteAudioTrack(remote_audio_track_handle, user_id_str)
191
193
  # map to localuser to save reference
@@ -297,6 +297,63 @@ class VideoFrame():
297
297
  alpha_buffer: bytearray = None
298
298
  alpha_mode: int = 0
299
299
 
300
+ @dataclass(kw_only=True)
301
+ class AiNsConfig:
302
+ ns_enabled: bool = True
303
+ ai_ns_enabled: bool = True
304
+ ai_ns_model_pref: int = 10
305
+ nsng_alg_route: int = 12
306
+ nsng_predef_agg: int = 11
307
+ @dataclass(kw_only=True)
308
+ class AiAecConfig:
309
+ enabled: bool = False
310
+ split_srate_for_48k: int = 16000
311
+
312
+ @dataclass(kw_only=True)
313
+ class BghvsCConfig:
314
+ enabled: bool = True
315
+ vad_thr: float = 0.8
316
+
317
+ @dataclass(kw_only=True)
318
+ class AgcConfig:
319
+ enabled: bool = False
320
+
321
+ @dataclass(kw_only=True)
322
+ class APMConfig:
323
+ ai_ns_config: AiNsConfig = field(default_factory=AiNsConfig)
324
+ ai_aec_config: AiAecConfig = field(default_factory=AiAecConfig)
325
+ bghvs_c_config: BghvsCConfig = field(default_factory=BghvsCConfig)
326
+ agc_config: AgcConfig = field(default_factory=AgcConfig)
327
+ enable_dump: bool = False
328
+
329
+ def _to_json_string(self):
330
+ import json
331
+ config_dict = {
332
+ "aec": {
333
+ "enabled": self.ai_aec_config.enabled,
334
+ "split_srate_for_48k": self.ai_aec_config.split_srate_for_48k
335
+ },
336
+ "bghvs": {
337
+ "enabled": self.bghvs_c_config.enabled,
338
+ "vadThr": self.bghvs_c_config.vad_thr
339
+ },
340
+ "agc": {
341
+ "enabled": self.agc_config.enabled
342
+ },
343
+ "ans": {
344
+ "enabled": self.ai_ns_config.ns_enabled
345
+ },
346
+ "sf_st_cfg": {
347
+ "enabled": self.ai_ns_config.ai_ns_enabled,
348
+ "ainsModelPref": self.ai_ns_config.ai_ns_model_pref
349
+ },
350
+ "sf_ext_cfg": {
351
+ "nsngAlgRoute": self.ai_ns_config.nsng_alg_route,
352
+ "nsngPredefAgg": self.ai_ns_config.nsng_predef_agg
353
+ }
354
+ }
355
+ return json.dumps(config_dict, separators=(',', ':'))
356
+
300
357
 
301
358
  @dataclass(kw_only=True)
302
359
  class AgoraServiceConfig:
@@ -324,6 +381,11 @@ class AgoraServiceConfig:
324
381
  log_file_size_kb: int = 5*1024
325
382
  data_dir: str = ""
326
383
  config_dir: str = "" #format like: "./agora_rtc_log"
384
+ #20251110 Fusion version: with apm filter
385
+ enable_apm: bool = False
386
+ apm_config: APMConfig = None
387
+
388
+
327
389
 
328
390
 
329
391
  @dataclass(kw_only=True)
@@ -566,4 +628,4 @@ class CapabilityItemMap:
566
628
  @dataclass(kw_only=True)
567
629
  class Capabilities:
568
630
  item_map: CapabilityItemMap = None
569
- capability_type: int = 0
631
+ capability_type: int = 0
@@ -65,6 +65,14 @@ agora_service_set_log_filter = agora_lib.agora_service_set_log_filter
65
65
  agora_service_set_log_filter.restype = AGORA_API_C_INT
66
66
  agora_service_set_log_filter.argtypes = [AGORA_HANDLE, ctypes.c_uint]
67
67
 
68
+ agora_audio_track_enable_audio_filter = agora_lib.agora_audio_track_enable_audio_filter
69
+ agora_audio_track_enable_audio_filter.restype = ctypes.c_int
70
+ agora_audio_track_enable_audio_filter.argtypes = [AGORA_HANDLE, ctypes.c_char_p, ctypes.c_int, ctypes.c_int]
71
+
72
+ agora_audio_track_set_filter_property = agora_lib.agora_audio_track_set_filter_property
73
+ agora_audio_track_set_filter_property.restype = ctypes.c_int
74
+ agora_audio_track_set_filter_property.argtypes = [AGORA_HANDLE, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int]
75
+
68
76
 
69
77
  class AgoraService:
70
78
  def __init__(self) -> None:
@@ -74,6 +82,8 @@ class AgoraService:
74
82
  self.inited = False
75
83
  #default to None, and never create it manually by developer from ver2.3.0
76
84
  self.media_node_factory = None
85
+ self.enable_apm = False
86
+ self.apm_config = None
77
87
 
78
88
  def initialize(self, config: AgoraServiceConfig):
79
89
  if self.inited == True:
@@ -101,7 +111,14 @@ class AgoraService:
101
111
 
102
112
  # force audio vad v2 to be enabled
103
113
  agora_parameter.set_parameters("{\"che.audio.label.enable\": true}")
104
-
114
+ # for apm filter: to enable apm filter
115
+ generator = "audio_processing_remote_playback"
116
+ cgenerator = generator.encode('utf-8')
117
+ ctrak = ctypes.c_char_p(None)
118
+ result = agora_service_enable_extension(self.service_handle, cprovider, cgenerator, ctrak, 1)
119
+ if result != 0:
120
+ logger.error(f"Failed to enable audio processing remote playback filter. Error code: {result}")
121
+
105
122
  #versio 2.2.0 for callback when muted
106
123
  if config.should_callbck_when_muted > 0:
107
124
  agora_parameter.set_parameters("{\"rtc.audio.enable_user_silence_packet\": true}")
@@ -112,6 +129,17 @@ class AgoraService:
112
129
  '''
113
130
  agora_parameter.set_parameters("{\"che.video.min_enc_level\": 0}")
114
131
 
132
+ #keep & save apm config
133
+ self.enable_apm = config.enable_apm
134
+ if self.enable_apm:
135
+ if config.apm_config is None:
136
+ self.apm_config = APMConfig()
137
+ else:
138
+ self.apm_config = config.apm_config
139
+ else:
140
+ self.apm_config = None
141
+
142
+
115
143
  return result
116
144
 
117
145
  def release(self):
@@ -210,3 +238,27 @@ class AgoraService:
210
238
  else:
211
239
  logger.error(f"Failed to set log file. Error code: {result}")
212
240
  return result
241
+ #apm related:
242
+ #apm related api
243
+ def _get_audio_filter_position(is_local_track: bool = False) -> int:
244
+ if is_local_track:
245
+ return 3
246
+ return 2
247
+
248
+ def _enable_audio_filter_by_track(track: any, name: str, enable: bool, is_local_track: bool) -> int:
249
+ if track is None:
250
+ return -1000
251
+ c_name = ctypes.c_char_p(name.encode('utf-8'))
252
+ c_enable = ctypes.c_int(0)
253
+ if enable:
254
+ c_enable = ctypes.c_int(1)
255
+ position = _get_audio_filter_position(is_local_track)
256
+ return int(agora_audio_track_enable_audio_filter(track, c_name, c_enable, ctypes.c_int(position)))
257
+ def _set_filter_property_by_track(track: any, name: str, key: str, value: str, is_local_track: bool) -> int:
258
+ if track is None:
259
+ return -1000
260
+ c_name = ctypes.c_char_p(name.encode('utf-8'))
261
+ c_key = ctypes.c_char_p(key.encode('utf-8'))
262
+ c_value = ctypes.c_char_p(value.encode('utf-8'))
263
+ position = _get_audio_filter_position(is_local_track)
264
+ return int(agora_audio_track_set_filter_property(track, c_name, c_key, c_value, ctypes.c_int(position)))
@@ -624,3 +624,7 @@ class LocalUser:
624
624
  size = len(data)
625
625
  ret = agora_local_user_send_aduio_meta_data(self.user_handle, c_data, ctypes.c_size_t(size))
626
626
  return ret
627
+ def _set_apm_filter_properties(self, remote_audio_track_handle, user_id_str)->int:
628
+ ret = self.connection._set_apm_filter_properties(remote_audio_track_handle, user_id_str)
629
+ print(f"**********LocalUser _set_apm_filter_properties: {ret}")
630
+ return ret
@@ -2,7 +2,7 @@ import time
2
2
  import ctypes
3
3
 
4
4
  from .agora_base import *
5
- from .agora_service import AgoraService
5
+ from .agora_service import AgoraService, _set_filter_property_by_track
6
6
  from .local_user import LocalUser
7
7
  from .rtc_connection_observer import IRTCConnectionObserver
8
8
  from ._ctypes_handle._audio_frame_observer import AudioFrameObserverInner
@@ -514,6 +514,32 @@ class RTCConnection:
514
514
  if self._capabilities_observer_obj:
515
515
  self._capabilities_observer_obj = None
516
516
  return 0
517
+
518
+ def _set_apm_filter_properties(self, remote_audio_track_handle, user_id_str)->int:
519
+ ret = -1000
520
+ is_enabled = self.rtc_engine.enable_apm
521
+ if not is_enabled:
522
+ return -1001
523
+ apm_config = self.rtc_engine.apm_config
524
+ if apm_config is None:
525
+ return -1002
526
+ apm_config_json = apm_config._to_json_string()
527
+ if apm_config_json is None:
528
+ return -1003
529
+ #Load AINS resource
530
+ ret = _set_filter_property_by_track(remote_audio_track_handle, "audio_processing_remote_playback", "apm_load_resource", "ains", False)
531
+
532
+ print(f"**********APM: to set apm_load_resource, error: {ret}")
533
+
534
+
535
+ #Set APM configuration
536
+ ret = _set_filter_property_by_track(remote_audio_track_handle, "audio_processing_remote_playback", "apm_config", apm_config_json, False)
537
+ print(f"**********APM: to set apm_config, error: {ret}, apm_config_json: {apm_config_json}")
538
+ #Enable dump
539
+ if apm_config.enable_dump:
540
+ ret = _set_filter_property_by_track(remote_audio_track_handle, "audio_processing_remote_playback", "apm_dump", "true", False)
541
+ print(f"**********APM: to enable apm_dump, error: {ret}")
542
+ return ret
517
543
 
518
544
 
519
545
 
@@ -3,6 +3,7 @@ import time
3
3
  from datetime import datetime
4
4
  import logging
5
5
  import os
6
+ import struct
6
7
  from agora.rtc.agora_base import AudioFrame
7
8
  logger = logging.getLogger(__name__)
8
9
 
@@ -11,6 +12,42 @@ logger = logging.getLogger(__name__)
11
12
  """
12
13
  ## VadDump helper class
13
14
  """
15
+ #buffer manager for vaddump: high performance buffer manager for vaddump
16
+
17
+ class BufferManager:
18
+ """High-performance buffer manager using struct.pack
19
+
20
+ Performance benchmarks (320 bytes, 100k iterations):
21
+ - struct.pack: 0.016-0.019 seconds (FASTEST)
22
+ - ctypes.memset: 0.107 seconds (5.6x slower)
23
+ - ctypes loop: 0.686 seconds (36x slower)
24
+ """
25
+
26
+ def __init__(self, initial_size=320):
27
+ self.size = initial_size
28
+ self.buffer = bytearray(self.size)
29
+ def resize(self, new_size: int):
30
+ if new_size != self.size:
31
+ self.size = new_size
32
+ self.buffer = bytearray(self.size)
33
+ def fill_zero(self):
34
+ self.fill_int16(0)
35
+
36
+ def fill_int16(self, value: int):
37
+ """Fill buffer with int16 value using struct.pack (fastest method)
38
+
39
+ Args:
40
+ value: int16 value to fill (-32768 to 32767)
41
+
42
+ Returns:
43
+ bytearray: The filled buffer
44
+ """
45
+ # Pack value as little-endian int16 and repeat
46
+ pattern = struct.pack('<h', value)
47
+ self.buffer[:] = pattern * (self.size // 2)
48
+ return self.buffer
49
+
50
+
14
51
  class VadDump():
15
52
  def __init__(self, path: str) -> None:
16
53
  self._file_path = path
@@ -20,6 +57,10 @@ class VadDump():
20
57
  self._source_file = None
21
58
  self._label_file = None
22
59
  self._vad_file = None
60
+ self._voice_prob_file = None
61
+ self._rms_file = None
62
+ self._pitch_file = None
63
+ self._buffer_manager = BufferManager(320)
23
64
  #check path is existed or not? if not, create new dir
24
65
  if self._check_directory_exists(path) is False:
25
66
  os.makedirs(path)
@@ -56,6 +97,16 @@ class VadDump():
56
97
  #open label file
57
98
  label_file_path = self._file_path + "/label.txt"
58
99
  self._label_file = open(label_file_path, "w")
100
+ #rms
101
+ rms_file_path = self._file_path + "/rms.pcm"
102
+ self._rms_file = open(rms_file_path, "wb")
103
+ #pitch
104
+ pitch_file_path = self._file_path + "/pitch.pcm"
105
+ self._pitch_file = open(pitch_file_path, "wb")
106
+ #voice prob
107
+ voice_prob_file_path = self._file_path + "/voice_prob.pcm"
108
+ self._voice_prob_file = open(voice_prob_file_path, "wb")
109
+
59
110
  #open vad file
60
111
  pass
61
112
  def write(self, frame:AudioFrame, vad_result_bytes: bytearray, vad_result_state : int) -> None:
@@ -68,6 +119,20 @@ class VadDump():
68
119
  if self._label_file:
69
120
  label_str = "ct:%d fct:%d state:%d far:%d vop:%d rms:%d pitch:%d mup:%d\n" % (self._count, self._frame_count,vad_result_state, frame.far_field_flag, frame.voice_prob, frame.rms, frame.pitch, frame.music_prob)
70
121
  self._label_file.write(label_str)
122
+ #adjut buffer size if needed
123
+ self._buffer_manager.resize(len(frame.buffer))
124
+ #write rms to buffer
125
+ self._buffer_manager.fill_int16(frame.rms*127)
126
+ if self._rms_file:
127
+ self._rms_file.write(self._buffer_manager.buffer)
128
+ #write pitch to buffe
129
+ self._buffer_manager.fill_int16(frame.pitch)
130
+ if self._pitch_file:
131
+ self._pitch_file.write(self._buffer_manager.buffer)
132
+ #write voice prob to buffer
133
+ self._buffer_manager.fill_int16(frame.voice_prob*127*127)
134
+ if self._voice_prob_file:
135
+ self._voice_prob_file.write(self._buffer_manager.buffer)
71
136
  #write to vad result
72
137
  if vad_result_state == 1: # start speaking
73
138
  #open new vad file and write header
@@ -96,6 +161,19 @@ class VadDump():
96
161
  self._label_file = None
97
162
  self._close_vad_file()
98
163
 
164
+ if self._source_file:
165
+ self._source_file.close()
166
+ self._source_file = None
167
+ if self._rms_file:
168
+ self._rms_file.close()
169
+ self._rms_file = None
170
+ if self._pitch_file:
171
+ self._pitch_file.close()
172
+ self._pitch_file = None
173
+ if self._voice_prob_file:
174
+ self._voice_prob_file.close()
175
+ self._voice_prob_file = None
176
+
99
177
  # assign to None
100
178
  self._count = 0
101
179
  self._frame_count = 0
@@ -10,8 +10,8 @@ import logging
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
12
  class AudioVadConfigV2():
13
- def __init__(self, preStartRecognizeCount:int, startRecognizeCount:int, stopRecognizeCount:int,
14
- activePercent:float, inactivePercent:float, start_voiceprob: int, stop_voiceporb:int, rmsThreshold:float):
13
+ def __init__(self, preStartRecognizeCount:int=16, startRecognizeCount:int=30, stopRecognizeCount:int=50,
14
+ activePercent:float=0.7, inactivePercent:float=0.5, start_voiceprob: int=70, stop_voiceporb:int=50, rmsThreshold:int=-50):
15
15
  self.start_recognize_count = startRecognizeCount
16
16
  self.pre_start_recognize_count = preStartRecognizeCount
17
17
  self.stop_recognize_count = stopRecognizeCount
@@ -31,8 +31,12 @@ class AudioVadConfigV2():
31
31
  # In a quiet environment, it can be set to -50;
32
32
  # in a noisy environment, it can be set to a value between -40 and -30.
33
33
 
34
- self.start_rms = rmsThreshold #default to -50
35
- self.stop_rms = rmsThreshold #default to -50
34
+ self.start_rms = rmsThreshold #default to -70
35
+ self.stop_rms = rmsThreshold #default to -70
36
+ # for apm mode:
37
+ self.enable_adaptive_rms_threshold : bool = True
38
+ self.adaptive_rms_threshold_factor : float = 0.67 #default to 0.67, and 2/3 to pass
39
+ print(f"vadconfigure start_rms: {self.start_rms}, stop_rms: {self.stop_rms}")
36
40
 
37
41
  pass
38
42
 
@@ -63,11 +67,17 @@ class AudioVadV2():
63
67
  #trend queue: not impl in this version date: 2024-10-29
64
68
  self._trend_queue = None #deque(maxlen=self._vad_configure.stop_recognize_count)
65
69
  self._trend_window = self._vad_configure.stop_recognize_count//2
70
+ self._voice_count : int = 0
71
+ self._silence_count : int = 0
72
+ self._total_voice_rms : int = 0
73
+ self._ref_avg_rms_in_last_session : int = 0
74
+
75
+ #convert rms from -120, 0 to range from 0 to 127, respond to db: -127db, to 0db
76
+ self._vad_configure.start_rms = 127 + self._vad_configure.start_rms
77
+ self._vad_configure.stop_rms = 127 + self._vad_configure.stop_rms
78
+ print(f"vad instance start_rms: {self._vad_configure.start_rms}, stop_rms: {self._vad_configure.stop_rms}")
79
+ pass
66
80
 
67
-
68
-
69
-
70
-
71
81
  def _push_to_start(self, data: VadDataV2) -> tuple[int,bool]:
72
82
  self._start_queue.append(data)
73
83
  size = len(self._start_queue)
@@ -140,19 +150,30 @@ class AudioVadV2():
140
150
  size, full = self._push_to_start(data)
141
151
  state = self._cur_state
142
152
  bytes = bytearray()
153
+
154
+ if data._is_activity == True:
155
+ self._voice_count += 1
156
+ self._total_voice_rms += data._audio_frame.rms
157
+ else:
158
+ self._voice_count = 0
159
+ self._total_voice_rms = 0
160
+
161
+
143
162
 
144
163
 
145
164
  if full == True:
146
- #存在一定的问题:如果pre中就已经是开始在说话了,这个时候就会出现问题,或者漏掉的情况
147
- #检查start中的比例是否符合阈值,如果符合阈值,zhi,则将start中的数据全部送入到pre中,并且将pre清空,同时将start清空,同时将当前状态设置为speaking
148
- total, silence_count = self._get_silence_count(self._start_queue, self._vad_configure.pre_start_recognize_count)
149
- total -= self._vad_configure.pre_start_recognize_count
150
- if (total - silence_count) / total >= self._vad_configure.activePercent:
165
+ if self._voice_count >= self._vad_configure.start_recognize_count:
151
166
  state = self._vad_state_startspeaking
152
167
  #move pre & start to a new bytearray
153
168
 
154
169
  self._move_deque(bytes, self._start_queue)
155
170
  self._clear_queue(self._start_queue)
171
+
172
+ #update ref
173
+ self._ref_avg_rms_in_last_session = int(self._total_voice_rms / self._voice_count)
174
+ self._voice_count = 0
175
+ self._total_voice_rms = 0
176
+ self._silence_count = 0
156
177
 
157
178
  #and clear pre &start
158
179
  self._clear_queue(self._stop_queue)
@@ -167,16 +188,19 @@ class AudioVadV2():
167
188
  size, full = self._push_to_stop(data)
168
189
  #print(f"stop: {size}, {full}")
169
190
 
191
+ if data._is_activity == True:
192
+ self._silence_count = 0
193
+ else:
194
+ self._silence_count += 1
195
+
170
196
 
171
- if full == True:
172
- #trend check
173
- trend = self._get_trend(self._stop_queue)
174
- #检查stop中的比例是否符合阈值,
175
- # 如果符合阈值,同时清空stop 清空,并且将当前状态设置为non-speaking
176
- total, silence_count = self._get_silence_count(self._stop_queue,0)
177
- if (silence_count) / total >= (self._vad_configure.inactivePercent):
197
+ if full == True:
198
+ if self._silence_count >= (self._vad_configure.stop_recognize_count):
178
199
  state = self._vad_state_stopspeaking
179
200
  self._clear_queue(self._stop_queue)
201
+ self._voice_count = 0
202
+ self._total_voice_rms = 0
203
+ self._silence_count = 0
180
204
  #print(f"stop speaking: {len(self._start_queue)}, {silence_count}, {total}, {trend}")
181
205
  return state, data._audio_frame.buffer
182
206
 
@@ -218,6 +242,11 @@ class AudioVadV2():
218
242
  #default: shoud never happen
219
243
  return int(-100), bytearray()
220
244
 
245
+ def _get_ref_active_avg_rms(self) -> int:
246
+ if self._vad_configure.enable_adaptive_rms_threshold and self._ref_avg_rms_in_last_session > 0:
247
+ return int(float(self._ref_avg_rms_in_last_session)*self._vad_configure.adaptive_rms_threshold_factor) #half of avg as threshold value
248
+ return self._vad_configure.start_rms
249
+
221
250
  """
222
251
  def _is_vad_active(self, data: AudioFrame) -> bool:
223
252
  """
@@ -235,6 +264,11 @@ class AudioVadV2():
235
264
  #case2
236
265
  #if data.far_field_flag == 1 and data.voice_prob > voice_prob :#and data.pitch > 0 : #voice: from 75 to 50
237
266
  #case4: rms > -40
238
- if data.far_field_flag == 1 and data.voice_prob > voice_prob and data.rms > rms_prob :#and data.pitch > 0 : #voice: from 75 to 50
267
+ #if data.far_field_flag == 1 and data.voice_prob > voice_prob and data.rms > rms_prob :#and data.pitch > 0 : #voice: from 75 to 50
268
+ #date: 2025-10-29 for sdk which support apm filter, and in this version, we don't need to use farfield flag to detect speech, so we don't need to use farfield flag to detect speech
269
+ refRms = self._get_ref_active_avg_rms()
270
+ active = (data.voice_prob == 1) or (data.rms > refRms)
271
+ #print(f"active: {active}, voice_prob: {data.voice_prob}, rms: {data.rms}, refRms: {refRms}")
272
+ if active:
239
273
  return True
240
274
  return False
@@ -30,6 +30,7 @@ try:
30
30
  elif sys.platform == 'linux':
31
31
  lib_agora_rtm_path = os.path.join(sdk_library_dir, 'libagora_rtm_sdk_c.so')
32
32
  rtm_lib = ctypes.CDLL(lib_agora_rtm_path)
33
+ ctypes.CDLL(os.path.join(sdk_library_dir, 'libagora_rtm_sdk.so'))
33
34
  except OSError as e:
34
35
  logger.error(f"Error loading the library: {e}")
35
36
  logger.error(f"Attempted to load from: {lib_agora_rtm_path}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: agora_python_server_sdk
3
- Version: 2.3.3
3
+ Version: 2.4.0
4
4
  Summary: A Python SDK for Agora Server
5
5
  Home-page: https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK
6
6
  Classifier: Intended Audience :: Developers
@@ -64,6 +64,25 @@ python agora_rtc/examples/example_audio_pcm_send.py --appId=xxx --channelId=xxx
64
64
  ```
65
65
 
66
66
  # Change log
67
+ ## 2025.11.18 Release version 2.4.0
68
+ -- Update SDK to APM version: 4.4.32/1025
69
+ -- Add configure support for enabling/disabling APM
70
+ -- Update methods in setup.py and __init__.py, ok, including version/URL, md5, etc.
71
+ -- Overall code pipeline ok, needs testing?? ok
72
+ -- Add support to rtm, one single sdk supports both rtm and rtc, ok
73
+ -- todo:
74
+ -[] Need to add VAD algorithm update ok
75
+ -[] Need to add vad_dump modifications ok
76
+ -[] Modify APM algorithm to support VAD switch, ok
77
+ -[] Add VAD configure parameter settings, ok
78
+ -[] Download every time, check md5 mismatch?? ok
79
+ NOTE:
80
+ APM features, i.e., server-side echo cancellation, noise suppression, automatic gain control, background voice removal, etc.
81
+ Normally, AEC/AINS/AGC, etc., are already implemented on the client side, and the server side does not need to implement them again, unless there are special requirements.
82
+ If you want to enable APM features, please contact Agora technical support.
83
+ NOTE:
84
+ How to use rtc,please ref to: https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK/tree/main/agora_rtc/examples
85
+ Hot to use rtm,please ref to ://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK/tree/main/agora_rtc/rtm_examples
67
86
  # 2025.11.07 release 2.3.3
68
87
  -- update: to support rtm
69
88
  -- adjust sdk's directory structure
@@ -2,6 +2,7 @@ MANIFEST.in
2
2
  README.md
3
3
  pyproject.toml
4
4
  setup.py
5
+ agora/__init__.py
5
6
  agora/rtc/__init__.py
6
7
  agora/rtc/agora_base.py
7
8
  agora/rtc/agora_parameter.py
@@ -80,6 +80,12 @@ class CustomInstallCommand(install):
80
80
  url = "https://download.agora.io/sdk/release/agora_rtc_sdk-x86_64-linux-gnu-v4.4.32-20250829_160340-860733-aed_20251107_1642.zip"
81
81
  if sys.platform == 'darwin':
82
82
  url = "https://download.agora.io/sdk/release/agora_sdk_mac_v4.4.32_25418_FULL_20250829_1647_860754-aed_20251107_1639.zip"
83
+
84
+ #20251110 Fusion version: with apm filter
85
+ mac_sdk="https://download.agora.io/sdk/release/agora_sdk_mac_v4.4.30_25869_FULL_20251030_1836_953684-aed.zip"
86
+ url = "https://download.agora.io/sdk/release/agora_rtc_sdk_x86_64-linux-gnu-v4.4.32.150_26715_SERVER_20251030_1807-aed.zip"
87
+ if sys.platform == 'darwin':
88
+ url = mac_sdk
83
89
 
84
90
 
85
91
  if arch == "aarch64" and sys.platform == 'linux':
@@ -107,12 +113,12 @@ class CustomInstallCommand(install):
107
113
 
108
114
  setup(
109
115
  name='agora_python_server_sdk',
110
- version='2.3.3',
116
+ version='2.4.0',
111
117
  description='A Python SDK for Agora Server',
112
118
  long_description=open('README.md').read(),
113
119
  long_description_content_type='text/markdown',
114
120
  url='https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK',
115
- packages=["agora.rtc", "agora.rtc._ctypes_handle", "agora.rtc._utils","agora.rtc.utils","agora.rtm","agora.rtm._ctypes_handle"],
121
+ packages=["agora", "agora.rtc", "agora.rtc._ctypes_handle", "agora.rtc._utils","agora.rtc.utils","agora.rtm","agora.rtm._ctypes_handle"],
116
122
  classifiers=[
117
123
  "Intended Audience :: Developers",
118
124
  'License :: OSI Approved :: MIT License',