agora-python-server-sdk 2.4.0__tar.gz → 2.4.2__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 (49) hide show
  1. {agora_python_server_sdk-2.4.0/agora_python_server_sdk.egg-info → agora_python_server_sdk-2.4.2}/PKG-INFO +63 -26
  2. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/README.md +62 -25
  3. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/__init__.py +33 -5
  4. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/_ctypes_handle/_ctypes_data.py +46 -1
  5. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/_ctypes_handle/_rtc_connection_observer.py +1 -1
  6. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/agora_base.py +13 -0
  7. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/agora_service.py +11 -5
  8. agora_python_server_sdk-2.4.2/agora/rtc/external_audio_processor.py +222 -0
  9. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/local_user.py +20 -5
  10. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/rtc_connection.py +89 -5
  11. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2/agora_python_server_sdk.egg-info}/PKG-INFO +63 -26
  12. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora_python_server_sdk.egg-info/SOURCES.txt +1 -0
  13. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/setup.py +14 -1
  14. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/MANIFEST.in +0 -0
  15. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/__init__.py +0 -0
  16. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/_ctypes_handle/_audio_frame_observer.py +0 -0
  17. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/_ctypes_handle/_local_user_observer.py +0 -0
  18. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/_ctypes_handle/_video_encoded_frame_observer.py +0 -0
  19. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/_ctypes_handle/_video_frame_observer.py +0 -0
  20. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/_utils/globals.py +0 -0
  21. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/agora_parameter.py +0 -0
  22. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/audio_encoded_frame_sender.py +0 -0
  23. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/audio_frame_observer.py +0 -0
  24. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/audio_pcm_data_sender.py +0 -0
  25. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/audio_sessionctrl.py +0 -0
  26. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/audio_vad_manager.py +0 -0
  27. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/local_audio_track.py +0 -0
  28. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/local_user_observer.py +0 -0
  29. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/local_video_track.py +0 -0
  30. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/media_node_factory.py +0 -0
  31. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/remote_audio_track.py +0 -0
  32. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/remote_video_track.py +0 -0
  33. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/rtc_connection_observer.py +0 -0
  34. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/utils/audio_consumer.py +0 -0
  35. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/utils/vad_dump.py +0 -0
  36. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/video_encoded_frame_observer.py +0 -0
  37. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/video_encoded_image_sender.py +0 -0
  38. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/video_frame_observer.py +0 -0
  39. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/video_frame_sender.py +0 -0
  40. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtc/voice_detection.py +0 -0
  41. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtm/__init__.py +0 -0
  42. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtm/_ctypes_handle/_ctypes_data.py +0 -0
  43. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtm/rtm_base.py +0 -0
  44. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtm/rtm_client.py +0 -0
  45. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora/rtm/rtm_event_handler.py +0 -0
  46. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora_python_server_sdk.egg-info/dependency_links.txt +0 -0
  47. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/agora_python_server_sdk.egg-info/top_level.txt +0 -0
  48. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/pyproject.toml +0 -0
  49. {agora_python_server_sdk-2.4.0 → agora_python_server_sdk-2.4.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: agora_python_server_sdk
3
- Version: 2.4.0
3
+ Version: 2.4.2
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
@@ -38,7 +38,7 @@ Description-Content-Type: text/markdown
38
38
  # Required Operating Systems and Python Versions
39
39
  - Supported Linux versions:
40
40
  - Ubuntu 18.04 LTS and above
41
- - CentOS 7.0 and above
41
+ - CentOS 8.0 and above
42
42
 
43
43
  - Supported Mac versions:
44
44
  - MacOS 13 and above(only for coding and testing)
@@ -57,40 +57,77 @@ pip install agora_python_server_sdk
57
57
  - Download and unzip [test_data.zip](https://download.agora.io/demo/test/test_data_202408221437.zip) to the Agora-Python-Server-SDK directory.
58
58
 
59
59
  ## Executing Test Script
60
- or linux os, should set env to :/site_packages/agora/agora_sdk/, like:
60
+ for linux os, should set env to :/site_packages/agora/agora_sdk/, like:
61
61
  export LD_LIBRARY_PATH=/site_packages/agora/agora_sdk/
62
62
  ```
63
63
  python agora_rtc/examples/example_audio_pcm_send.py --appId=xxx --channelId=xxx --userId=xxx --audioFile=./test_data/demo.pcm --sampleRate=16000 --numOfChannels=1
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
+ ## 2025.12.29 Release Version 2.4.2
68
+
69
+ - Added **incremental send mode** support.
70
+ - New `connection::send_intra_request` API, allowing you to initiate an intra request to remote users and trigger them to send a key frame.
71
+
72
+ #### Example usage of incremental send mode
73
+
74
+ ```python
75
+ # Configure incremental send parameters
76
+ publish_config.send_external_audio_parameters = SendExternalAudioParameters(
77
+ enabled=True,
78
+ send_ms=2000,
79
+ send_speed=2,
80
+ deliver_mute_data_for_fake_adm=False
81
+ )
82
+
83
+ # Create an RTC connection
84
+ connection = agora_service.create_rtc_connection(con_config, publish_config)
85
+ ```
86
+
87
+
88
+
89
+ ## 2025.12.17 Release 2.4.1
90
+
91
+ - Updated RTC SDK to version 154.
92
+ - Added support for VAD (Voice Activity Detection) for external audio sources, including:
93
+ - **Background voice removal**
94
+ - **Noise suppression**
95
+ - **Echo cancellation**
96
+ - **Automatic gain control**
97
+ - **Other 3A algorithms**
98
+ All supported via the `external_Audio_Processor`.
99
+ - Modified LocalUser return values for clearer SDK error code distinction.
100
+ - Added `example_external_Audio_Processor.py` to demonstrate external audio data processing.
101
+
102
+ ---
103
+
104
+ ## 2025.11.18 Release 2.4.0
105
+
106
+ - Updated SDK to APM version: `4.4.32/1025`
107
+ - Added configuration support to enable/disable APM.
108
+ - Updated methods in `setup.py` and `__init__.py`, including version/URL and MD5 processing.
109
+ - Completed main code pipeline (**further testing recommended**).
110
+ - Added RTM support: one SDK now supports both RTC and RTM.
111
+
112
+ **To-Do Items:**
113
+ - [x] Add VAD algorithm update
114
+ - [x] Add vad_dump modifications
115
+ - [x] Add APM algorithm VAD switch support
116
+ - [x] Add VAD configuration parameter setting
117
+ - [x] Download and check for MD5 mismatch on every download
118
+
119
+ **Notes:**
120
+ - **APM features** include server-side echo cancellation, noise suppression, automatic gain control, background voice removal, and more.
121
+ - Typically, AEC/AINS/AGC, etc., are handled on the client; server-side activation is only necessary for specific requirements.
122
+ - If you need to enable APM, please contact Agora technical support.
123
+
124
+ **Usage Examples:**
125
+ - [RTC usage example](https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK/tree/main/agora_rtc/examples)
126
+ - [RTM usage example](https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK/tree/main/agora_rtc/rtm_examples)
86
127
  # 2025.11.07 release 2.3.3
87
128
  -- update: to support rtm
88
129
  -- adjust sdk's directory structure
89
130
  -- change requests to urllib
90
- # 2025.10.23 release 2.3.2: support rtc and rtm in one package
91
- -- update: to support rtm.can support both rtc and rtm in one package.
92
- -- adjust sdk's directory structure
93
- -- update rtc sdk
94
131
  # 2025.10.09 release 2.3.1
95
132
  -- update arm64 rtc sdk:Fixed a JNI referencing issue in the previous arm64 build. This issue only outputs logs to the console and does not affect functionality.
96
133
 
@@ -23,7 +23,7 @@
23
23
  # Required Operating Systems and Python Versions
24
24
  - Supported Linux versions:
25
25
  - Ubuntu 18.04 LTS and above
26
- - CentOS 7.0 and above
26
+ - CentOS 8.0 and above
27
27
 
28
28
  - Supported Mac versions:
29
29
  - MacOS 13 and above(only for coding and testing)
@@ -42,40 +42,77 @@ pip install agora_python_server_sdk
42
42
  - Download and unzip [test_data.zip](https://download.agora.io/demo/test/test_data_202408221437.zip) to the Agora-Python-Server-SDK directory.
43
43
 
44
44
  ## Executing Test Script
45
- or linux os, should set env to :/site_packages/agora/agora_sdk/, like:
45
+ for linux os, should set env to :/site_packages/agora/agora_sdk/, like:
46
46
  export LD_LIBRARY_PATH=/site_packages/agora/agora_sdk/
47
47
  ```
48
48
  python agora_rtc/examples/example_audio_pcm_send.py --appId=xxx --channelId=xxx --userId=xxx --audioFile=./test_data/demo.pcm --sampleRate=16000 --numOfChannels=1
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
+ ## 2025.12.29 Release Version 2.4.2
53
+
54
+ - Added **incremental send mode** support.
55
+ - New `connection::send_intra_request` API, allowing you to initiate an intra request to remote users and trigger them to send a key frame.
56
+
57
+ #### Example usage of incremental send mode
58
+
59
+ ```python
60
+ # Configure incremental send parameters
61
+ publish_config.send_external_audio_parameters = SendExternalAudioParameters(
62
+ enabled=True,
63
+ send_ms=2000,
64
+ send_speed=2,
65
+ deliver_mute_data_for_fake_adm=False
66
+ )
67
+
68
+ # Create an RTC connection
69
+ connection = agora_service.create_rtc_connection(con_config, publish_config)
70
+ ```
71
+
72
+
73
+
74
+ ## 2025.12.17 Release 2.4.1
75
+
76
+ - Updated RTC SDK to version 154.
77
+ - Added support for VAD (Voice Activity Detection) for external audio sources, including:
78
+ - **Background voice removal**
79
+ - **Noise suppression**
80
+ - **Echo cancellation**
81
+ - **Automatic gain control**
82
+ - **Other 3A algorithms**
83
+ All supported via the `external_Audio_Processor`.
84
+ - Modified LocalUser return values for clearer SDK error code distinction.
85
+ - Added `example_external_Audio_Processor.py` to demonstrate external audio data processing.
86
+
87
+ ---
88
+
89
+ ## 2025.11.18 Release 2.4.0
90
+
91
+ - Updated SDK to APM version: `4.4.32/1025`
92
+ - Added configuration support to enable/disable APM.
93
+ - Updated methods in `setup.py` and `__init__.py`, including version/URL and MD5 processing.
94
+ - Completed main code pipeline (**further testing recommended**).
95
+ - Added RTM support: one SDK now supports both RTC and RTM.
96
+
97
+ **To-Do Items:**
98
+ - [x] Add VAD algorithm update
99
+ - [x] Add vad_dump modifications
100
+ - [x] Add APM algorithm VAD switch support
101
+ - [x] Add VAD configuration parameter setting
102
+ - [x] Download and check for MD5 mismatch on every download
103
+
104
+ **Notes:**
105
+ - **APM features** include server-side echo cancellation, noise suppression, automatic gain control, background voice removal, and more.
106
+ - Typically, AEC/AINS/AGC, etc., are handled on the client; server-side activation is only necessary for specific requirements.
107
+ - If you need to enable APM, please contact Agora technical support.
108
+
109
+ **Usage Examples:**
110
+ - [RTC usage example](https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK/tree/main/agora_rtc/examples)
111
+ - [RTM usage example](https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK/tree/main/agora_rtc/rtm_examples)
71
112
  # 2025.11.07 release 2.3.3
72
113
  -- update: to support rtm
73
114
  -- adjust sdk's directory structure
74
115
  -- change requests to urllib
75
- # 2025.10.23 release 2.3.2: support rtc and rtm in one package
76
- -- update: to support rtm.can support both rtc and rtm in one package.
77
- -- adjust sdk's directory structure
78
- -- update rtc sdk
79
116
  # 2025.10.09 release 2.3.1
80
117
  -- update arm64 rtc sdk:Fixed a JNI referencing issue in the previous arm64 build. This issue only outputs logs to the console and does not affect functionality.
81
118
 
@@ -95,7 +95,16 @@ def report_progress(blocknum, blocksize, totalsize):
95
95
  downloaded = blocknum * blocksize
96
96
  print(f"\rDownloading: ----{downloaded} bytes-----", end='', flush=True)
97
97
 
98
-
98
+ def _get_url_from_version_file(path: str):
99
+ if not os.path.exists(path):
100
+ return ""
101
+ try:
102
+ with open(path, 'r') as f:
103
+ lines = f.readline()
104
+ return lines
105
+ except Exception as e:
106
+ logger.error(f"Failed to read version file: {e}")
107
+ return ""
99
108
  def _check_download_and_extract_sdk():
100
109
  agora_service_path = os.path.dirname(os.path.abspath(__file__))
101
110
  # change from dir like: /home/xxx/agora_rtc/agora/rtc/agora_sdk to
@@ -125,12 +134,20 @@ def _check_download_and_extract_sdk():
125
134
  #20251110 Fusion version: with apm filter
126
135
  mac_sdk="https://download.agora.io/sdk/release/agora_sdk_mac_v4.4.30_25869_FULL_20251030_1836_953684-aed.zip"
127
136
  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"
137
+
138
+ linux_sdk = "https://download.agora.io/sdk/release/agora_rtc_sdk_x86_64-linux-gnu-Agora_Native_SDK_for_Linux_x64_zhourui_26895_SERVER_20251121_1628_987405_20251021_1427-3a.zip"
139
+ mac_sdk="https://download.agora.io/sdk/release/agora_sdk_mac_Agora_Native_SDK_for_Mac_zhourui_26101_FULL_20251121_2135_987705_20251021_1427-3a.zip"
140
+
141
+ linux_sdk="https://download.agora.io/sdk/release/agora_rtc_sdk_x86_64-linux-gnu-v4.4.32.154_26982_SERVER_20251210_1745_994155_20251021_1427-3a.zip"
142
+ mac_sdk="https://download.agora.io/sdk/release/agora_sdk_mac_v4.4.32.154_26308_FULL_20251210_1756_994156_20251021_1427-3a.zip"
143
+
144
+
128
145
 
129
146
 
130
147
  linux_libfile_path = os.path.join(sdk_library_dir, "libagora_rtc_sdk.so")
131
148
  mac_libfile_path = os.path.join(sdk_library_dir, "libAgoraRtcKit.dylib")
132
- linux_md5 = "821cb1a388279648fcb204ca795e6476"
133
- mac_md5 = "5b9940d3fca033a53ac30216d5c39be6"
149
+ linux_md5 = "e89043f0db667c207d9308d3515a67ed"
150
+ mac_md5 = "f63e8af2047a53643a1ceb0a4b24b802"
134
151
 
135
152
  #rtc_md5 = "7031dd10d1681cd88fd89d68c5b54282"
136
153
  url = linux_sdk
@@ -156,6 +173,7 @@ def _check_download_and_extract_sdk():
156
173
  url = "https://download.agora.io/sdk/release/Agora-RTC-aarch64-linux-gnu-v4.4.32-20251009_145437-921455_20251023_1538.zip"
157
174
  rtc_md5 = "5c002f25d2b381e353082da4f835b4f2"
158
175
 
176
+ '''
159
177
  is_file_exist = os.path.exists(rtc_libfile_path)
160
178
  if is_file_exist:
161
179
  md5_value = get_file_md5(rtc_libfile_path)
@@ -163,8 +181,15 @@ def _check_download_and_extract_sdk():
163
181
  md5_value = ""
164
182
  if md5_value == rtc_md5:
165
183
  return
184
+ '''
185
+ is_file_exist = os.path.exists(rtc_libfile_path)
186
+ version_url = _get_url_from_version_file(os.path.join(sdk_root_dir, "version.txt"))
187
+ if version_url == url and is_file_exist:
188
+ return
189
+
190
+ #change from md5 check to url check
166
191
 
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}")
192
+ logger.error(f"missing agora sdk, now download it, please wait for a while...: {rtc_libfile_path} {version_url} {url} {is_file_exist}")
168
193
  if os.path.exists(sdk_library_dir):
169
194
  os.system(f"rm -rf {sdk_library_dir}")
170
195
  os.makedirs(sdk_library_dir, exist_ok=True)
@@ -184,6 +209,9 @@ def _check_download_and_extract_sdk():
184
209
 
185
210
  if os.path.exists(zip_path):
186
211
  os.remove(zip_path)
212
+ #write version url to version.txt
213
+ with open(os.path.join(sdk_root_dir, "version.txt"), "w") as f:
214
+ f.write(url)
187
215
  logger.error("download done, continue...")
188
216
 
189
217
 
@@ -207,5 +235,5 @@ if not os.path.exists(rtc_libfile_path):
207
235
  logger.error(f"library {rtc_libfile_path} not found")
208
236
  sys.exit(1)
209
237
 
210
- # 显式导出这些变量,确保子模块可以导入
238
+ # Explicitly export these variables to ensure that submodules can import them
211
239
  __all__ = ['sdk_library_dir', 'sdk_rtc_dir', 'sdk_rtm_dir', 'sdk_root_dir']
@@ -1368,4 +1368,49 @@ class CapabilitiesItemMapInner(ctypes.Structure):
1368
1368
  return CapabilitiesItemMap(
1369
1369
  item=self.item,
1370
1370
  size=self.size
1371
- )
1371
+ )
1372
+
1373
+ class AudioSinkWantesInner(ctypes.Structure):
1374
+ _fields_ = [
1375
+ ("samples_per_sec", ctypes.c_int),
1376
+ ("channels", ctypes.c_uint32),
1377
+ ]
1378
+ pass
1379
+ class AudioLabelInner(ctypes.Structure):
1380
+ _fields_ = [
1381
+ ("far_filed_flag", ctypes.c_int),
1382
+ ("rms", ctypes.c_int),
1383
+ ("voice_prob", ctypes.c_int),
1384
+ ("music_prob", ctypes.c_int),
1385
+ ("pitch", ctypes.c_int),
1386
+ ]
1387
+ class AudioPcmFrameInner(ctypes.Structure):
1388
+ _fields_ = [
1389
+ ("capture_timestamp", ctypes.c_uint32),
1390
+ ("samples_per_channel", ctypes.c_uint32),
1391
+ ("sample_rate_hz", ctypes.c_int),
1392
+ ("num_channels", ctypes.c_uint32),
1393
+ ("bytes_per_sample", ctypes.c_uint32),
1394
+ ("data", ctypes.c_int16*3840),
1395
+ ("audio_label", AudioLabelInner),
1396
+ ]
1397
+ def get(self):
1398
+ return AudioFrame(
1399
+ type=0,
1400
+ samples_per_channel=self.samples_per_channel,
1401
+ bytes_per_sample=self.bytes_per_sample,
1402
+ channels=self.num_channels,
1403
+ samples_per_sec=self.sample_rate_hz,
1404
+ buffer=bytearray(ctypes.string_at(self.data, self.samples_per_channel * self.bytes_per_sample * self.num_channels)),
1405
+ render_time_ms=self.capture_timestamp,
1406
+ avsync_type=0,
1407
+ presentation_ms=0,
1408
+ audio_track_number=0,
1409
+ rtp_timestamp=0,
1410
+ far_field_flag=self.audio_label.far_filed_flag,
1411
+ rms=self.audio_label.rms,
1412
+ voice_prob=self.audio_label.voice_prob,
1413
+ music_prob=self.audio_label.music_prob,
1414
+ pitch=self.audio_label.pitch
1415
+ )
1416
+ pass
@@ -288,7 +288,7 @@ class CapabilitiesObserverInner(ctypes.Structure):
288
288
  self.on_capabilities_changed = ON_CAPABILITIES_CHANGED_CALLBACK(self._on_capabilities_changed)
289
289
 
290
290
  def _on_capabilities_changed(self, agora_capabilities_observer, ptr_caps_inner, size):
291
- print(f"ConnCB _on_capabilities_changed: {agora_capabilities_observer}, {ptr_caps_inner}, {size}")
291
+ logger.debug(f"ConnCB _on_capabilities_changed: {agora_capabilities_observer}, {ptr_caps_inner}, {size}")
292
292
 
293
293
  # 正确解析C指针数组
294
294
  # 方法1: 使用ctypes.cast将指针转换为数组
@@ -427,6 +427,18 @@ class SenderOptions:
427
427
  cc_mode: TCcMode = TCcMode.CC_ENABLED
428
428
  codec_type: VideoCodecType = VideoCodecType.VIDEO_CODEC_H264
429
429
 
430
+ '''
431
+ note: DeliverMuteDataForFakeAdm can only set to rtc engine level, can not
432
+ set to connection level
433
+ so if once a connection has set to true, wihich will affect all the connections,
434
+ '''
435
+ @dataclass(kw_only=True)
436
+ class SendExternalAudioParameters:
437
+ enabled: bool = False
438
+ send_ms: int = 0
439
+ send_speed: int = 0
440
+ deliver_mute_data_for_fake_adm: bool = False
441
+
430
442
 
431
443
  @dataclass(kw_only=True)
432
444
  class RtcConnectionPublishConfig:
@@ -437,6 +449,7 @@ class RtcConnectionPublishConfig:
437
449
  audio_publish_type: AudioPublishType = AudioPublishType.AUDIO_PUBLISH_TYPE_PCM
438
450
  video_publish_type: VideoPublishType = VideoPublishType.VIDEO_PUBLISH_TYPE_NONE
439
451
  video_encoded_image_sender_options: 'SenderOptions' = field(default_factory=SenderOptions)
452
+ send_external_audio_parameters: 'SendExternalAudioParameters | None' = None
440
453
 
441
454
  @dataclass(kw_only=True)
442
455
  class VideoSubscriptionOptions:
@@ -118,6 +118,12 @@ class AgoraService:
118
118
  result = agora_service_enable_extension(self.service_handle, cprovider, cgenerator, ctrak, 1)
119
119
  if result != 0:
120
120
  logger.error(f"Failed to enable audio processing remote playback filter. Error code: {result}")
121
+ if config.enable_apm:
122
+ generator = "audio_processing_pcm_source"
123
+ cgenerator = generator.encode('utf-8')
124
+ result = agora_service_enable_extension(self.service_handle, cprovider, cgenerator, ctrak, 1)
125
+ if result != 0:
126
+ logger.error(f"Failed to enable audio processing pcm source filter. Error code: {result}")
121
127
 
122
128
  #versio 2.2.0 for callback when muted
123
129
  if config.should_callbck_when_muted > 0:
@@ -180,11 +186,11 @@ class AgoraService:
180
186
  return RTCConnection(self, con_config, publish_config)
181
187
 
182
188
  # createCustomAudioTrackPcm: creatae a custom audio track from pcm data sender
183
- def _create_custom_audio_track_pcm(self, audio_pcm_data_sender: AudioPcmDataSender, scenario: AudioScenarioType) -> LocalAudioTrack:
189
+ def _create_custom_audio_track_pcm(self, audio_pcm_data_sender: AudioPcmDataSender, scenario: AudioScenarioType, is_extra_audio: bool) -> LocalAudioTrack:
184
190
  if not self.inited:
185
191
  logger.error("AgoraService is not initialized. Please call initialize() first.")
186
192
  return None
187
- if scenario == AudioScenarioType.AUDIO_SCENARIO_AI_SERVER:
193
+ if scenario == AudioScenarioType.AUDIO_SCENARIO_AI_SERVER and is_extra_audio == False:
188
194
  custom_audio_track = agora_service_create_direct_custom_audio_track_pcm(self.service_handle, audio_pcm_data_sender.sender_handle)
189
195
  else:
190
196
  custom_audio_track = agora_service_create_custom_audio_track_pcm(self.service_handle, audio_pcm_data_sender.sender_handle)
@@ -192,10 +198,10 @@ class AgoraService:
192
198
  return None
193
199
  local_track = LocalAudioTrack(custom_audio_track)
194
200
  #default for ai senario to set min delay to 10ms
195
- if scenario != AudioScenarioType.AUDIO_SCENARIO_AI_SERVER:
196
- local_track.set_send_delay_ms(10)
197
- local_track.set_max_buffer_audio_frame_number(100000)
201
+
202
+ local_track.set_max_buffer_audio_frame_number(100000)
198
203
  #and set enable to true
204
+ local_track.set_send_delay_ms(10)
199
205
  local_track.set_enabled(True)
200
206
  return local_track
201
207
  # mix_mode: MIX_ENABLED = 0, MIX_DISABLED = 1
@@ -0,0 +1,222 @@
1
+ import time
2
+ import ctypes
3
+
4
+ from .agora_base import *
5
+ from .agora_service import AgoraService, _set_filter_property_by_track, _enable_audio_filter_by_track
6
+ from .local_user import LocalUser
7
+ from .rtc_connection_observer import IRTCConnectionObserver
8
+ from ._ctypes_handle._audio_frame_observer import AudioFrameObserverInner
9
+ from .agora_parameter import AgoraParameter
10
+ from ._utils.globals import AgoraHandleInstanceMap
11
+ from ._ctypes_handle._rtc_connection_observer import RTCConnectionObserverInner, CapabilitiesObserverInner
12
+ from ._ctypes_handle._ctypes_data import *
13
+ from .utils.audio_consumer import PcmConsumeStats
14
+ from .voice_detection import AudioVadConfigV2, AudioVadV2
15
+ import logging
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+
20
+
21
+ class IAudioSinkObserver:
22
+ def on_processed_audio_frame(self,processor: 'ExternalAudioProcessor',frame: AudioFrame,vad_result_state:int,vad_result_data:bytearray):
23
+ pass
24
+
25
+ agora_audio_sink_create = agora_lib.agora_audio_sink_create
26
+ agora_audio_sink_create.restype = ctypes.c_void_p
27
+ agora_audio_sink_create.argtypes = [ctypes.c_void_p]
28
+
29
+ agora_audio_track_add_audio_sink = agora_lib.agora_audio_track_add_audio_sink
30
+ agora_audio_track_add_audio_sink.restype = ctypes.c_int
31
+ agora_audio_track_add_audio_sink.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(AudioSinkWantesInner)]
32
+
33
+ agora_audio_sink_destroy = agora_lib.agora_audio_sink_destroy
34
+ agora_audio_sink_destroy.restype = None
35
+ agora_audio_sink_destroy.argtypes = [ctypes.c_void_p]
36
+
37
+ #call back dec
38
+ ON_AUDIO_SINK_DATA_CALLBACK = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.POINTER(AudioPcmFrameInner))
39
+
40
+
41
+ class AudioSinkObserverInner(ctypes.Structure):
42
+ _fields_ = [
43
+ ("on_audio_sink_data", ON_AUDIO_SINK_DATA_CALLBACK),
44
+ ("user_data", ctypes.c_void_p),
45
+ ]
46
+ def __init__(self, processor: 'ExternalAudioProcessor'):
47
+ self.on_audio_sink_data = ON_AUDIO_SINK_DATA_CALLBACK(self._on_audio_sink_data)
48
+ self._processor = processor
49
+ pass
50
+ def _on_audio_sink_data(self, handle: ctypes.c_void_p, audio_pcm_frame_inner_ptr: ctypes.POINTER(AudioPcmFrameInner)):
51
+
52
+ #validity check
53
+ if audio_pcm_frame_inner_ptr is None:
54
+ return -1000
55
+
56
+ #convert to aduioframe, and get user data
57
+ pcm_frame = audio_pcm_frame_inner_ptr.contents
58
+
59
+ #conver to audioframe
60
+ audio_frame = pcm_frame.get()
61
+
62
+ if self._processor:
63
+ self._processor._do_result_frame(audio_frame)
64
+
65
+ return 0
66
+
67
+ class AudioSink:
68
+ def __init__(self, audio_processor: 'ExternalAudioProcessor'):
69
+ self._audio_sink_observer_inner = None
70
+ self._c_sink = None
71
+ self._audio_processor = audio_processor
72
+ pass
73
+ def release(self):
74
+ if self._c_sink:
75
+ agora_audio_sink_destroy(ctypes.c_void_p(self._c_sink))
76
+ self._c_sink = None
77
+ self._audio_sink_observer_inner = None
78
+ self._audio_processor = None
79
+ pass
80
+
81
+ class ExternalAudioProcessor:
82
+
83
+ def __init__(self, engine: AgoraService):
84
+ self._pcm_sender = None
85
+ self._local_audio_track = None
86
+ self._engine = engine
87
+ self._is_initialized = False
88
+ #do create sender and track
89
+ self._pcm_sender = self._engine.media_node_factory.create_audio_pcm_data_sender()
90
+ self._local_audio_track = self._engine._create_custom_audio_track_pcm(self._pcm_sender, AudioScenarioType.AUDIO_SCENARIO_DEFAULT)
91
+ self._audio_sink = self._create_audio_sink()
92
+ self._vad_instance = None
93
+ self._observer = None
94
+ pass
95
+ def initialize(self, apm_config: APMConfig, out_sample_rate: int, out_channels: int, vad_config: AudioVadConfigV2, observer: IAudioSinkObserver)->int:
96
+ if self._is_initialized:
97
+ return 0
98
+ if apm_config is None and vad_config is None:
99
+ logger.error("[ExternalAudioProcessor] apm_config and vad_config are both None")
100
+ return -1000
101
+ if out_sample_rate <= 0 or out_channels <= 0:
102
+ logger.error("[ExternalAudioProcessor] out_sample_rate or out_channels is invalid")
103
+ return -1001
104
+ if observer is None:
105
+ logger.error("[ExternalAudioProcessor] observer is None")
106
+ return -1002
107
+
108
+ #set filer properties
109
+ ret = self._set_filter_properties(apm_config)
110
+ if ret < 0:
111
+ logger.info(f"[ExternalAudioProcessor] Failed to set filter properties, error: {ret}")
112
+ return ret
113
+
114
+ #add sink
115
+ ret = self._add_audio_sink(out_sample_rate, out_channels)
116
+ if ret < 0:
117
+ logger.info(f"[ExternalAudioProcessor] Failed to add audio sink, error: {ret}")
118
+ return ret
119
+
120
+ #set track properties
121
+ self._local_audio_track.set_send_delay_ms(10)
122
+ self._local_audio_track.set_max_buffer_audio_frame_number(100000)
123
+ self._local_audio_track.set_enabled(True)
124
+
125
+ self._observer = observer
126
+
127
+ # create vad instance
128
+ if vad_config is not None:
129
+ self._vad_instance = AudioVadV2(vad_config)
130
+ return ret
131
+ pass
132
+
133
+ def push_audio_pcm_data(self, data, sample_rate, channels, start_pts:int=0)->int:
134
+ ret = -1000
135
+ if self._pcm_sender is None:
136
+ return -1001
137
+ readLen = len(data)
138
+ bytes_per_frame_in_ms = (sample_rate / 1000) * 2 * channels
139
+ remainder = readLen % bytes_per_frame_in_ms
140
+ if remainder != 0:
141
+ return -1002
142
+ pack_num_in_ms = readLen // bytes_per_frame_in_ms
143
+
144
+ frame = PcmAudioFrame()
145
+ frame.data = data
146
+ frame.sample_rate = sample_rate
147
+ frame.number_of_channels = channels
148
+ frame.bytes_per_sample = 2
149
+ frame.timestamp = 0
150
+ frame.samples_per_channel = readLen // (channels * 2)
151
+ frame.present_time_ms = start_pts
152
+
153
+ ret = self._pcm_sender.send_audio_pcm_data(frame)
154
+ return ret
155
+ def _create_audio_sink(self)->AudioSink:
156
+ audio_sink = AudioSink(self)
157
+ audio_sink_observer_inner = AudioSinkObserverInner(self)
158
+ audio_sink._audio_sink_observer_inner = audio_sink_observer_inner
159
+ audio_sink._c_sink = agora_audio_sink_create(ctypes.byref(audio_sink_observer_inner))
160
+ if audio_sink._c_sink is None:
161
+ return None
162
+
163
+ return audio_sink
164
+ def _add_audio_sink(self, out_sample_rate: int, out_channels: int)->int:
165
+ if self._audio_sink is None or self._audio_sink._c_sink is None:
166
+ return -1000
167
+
168
+ # call to c api
169
+ wants = AudioSinkWantesInner(samples_per_sec=out_sample_rate, channels=out_channels)
170
+ ret = agora_audio_track_add_audio_sink(self._local_audio_track.track_handle, self._audio_sink._c_sink, ctypes.byref(wants))
171
+ return ret
172
+ pass
173
+ def _set_filter_properties(self, apm_config: APMConfig)->int:
174
+ if self._audio_sink is None or self._audio_sink._c_sink is None:
175
+ return -2000
176
+ if apm_config is None:
177
+ return 0
178
+ #call to c api
179
+ track_handle = self._local_audio_track.track_handle
180
+ ret = _enable_audio_filter_by_track(track_handle, "audio_processing_pcm_source", True, True)
181
+ if ret != 0:
182
+ logger.info(f"[ExternalAudioProcessor] Failed to enable audio filter by track, error: {ret}")
183
+ return -2003
184
+
185
+ ret = _set_filter_property_by_track(track_handle, "audio_processing_pcm_source", "apm_load_resource", "ains", True)
186
+ if ret != 0:
187
+ logger.info(f"[ExternalAudioProcessor] Failed to set filter property by track, error: {ret}")
188
+ return -2004
189
+
190
+ apm_config_json = apm_config._to_json_string()
191
+ if apm_config_json is None:
192
+ logger.info(f"[ExternalAudioProcessor] Failed to get apm config json")
193
+ return -2005
194
+
195
+
196
+ ret = _set_filter_property_by_track(track_handle, "audio_processing_pcm_source", "apm_config", apm_config_json, True)
197
+ if ret != 0:
198
+ logger.info(f"[ExternalAudioProcessor]Failed to set filter property by track, error: {ret}")
199
+ return -2006
200
+ logger.info(f"[ExternalAudioProcessor] apm configure json: {apm_config_json}")
201
+
202
+ if apm_config.enable_dump:
203
+ ret = _set_filter_property_by_track(track_handle, "audio_processing_pcm_source", "apm_dump", "true", True)
204
+ if ret != 0:
205
+ logger.info(f"[ExternalAudioProcessor] Failed to set filter property by track, error: {ret}")
206
+ return -2007
207
+
208
+ return ret
209
+ def _do_result_frame(self, audio_frame: AudioFrame):
210
+ print(f"ExternalAudioProcessor _do_result_frame: audio_frame voice_prob {audio_frame.voice_prob}, rms {audio_frame.rms}, pitch {audio_frame.pitch}, far_field_flag {audio_frame.far_field_flag}")
211
+ ret = 0
212
+ data = None
213
+ if self._vad_instance is not None:
214
+ ret, data = self._vad_instance.process(audio_frame)
215
+ print(f"ExternalAudioProcessor _do_result_frame: vad result ret {ret}, data length {len(data)}")
216
+ #do callback now
217
+ if self._observer is not None:
218
+ self._observer.on_processed_audio_frame(self, audio_frame, ret, data)
219
+ pass
220
+
221
+
222
+
@@ -230,7 +230,9 @@ agora_local_user_send_aduio_meta_data = agora_lib.agora_local_user_send_audio_me
230
230
  agora_local_user_send_aduio_meta_data.restype = AGORA_API_C_INT
231
231
  agora_local_user_send_aduio_meta_data.argtypes = [AGORA_HANDLE, ctypes.c_char_p, ctypes.c_size_t]
232
232
 
233
-
233
+ agora_local_user_send_intra_request = agora_lib.agora_local_user_send_intra_request
234
+ agora_local_user_send_intra_request.restype = AGORA_API_C_INT
235
+ agora_local_user_send_intra_request.argtypes = [AGORA_HANDLE, ctypes.c_char_p]
234
236
 
235
237
 
236
238
  class LocalUser:
@@ -383,7 +385,7 @@ class LocalUser:
383
385
 
384
386
  def subscribe_audio(self, user_id):
385
387
  if user_id is None:
386
- return -1
388
+ return -1000
387
389
  uid_str = user_id.encode('utf-8')
388
390
  #ret = agora_local_user_subscribe_audio(self.user_handle, ctypes.create_string_buffer(uid_str))
389
391
  # note:both ctypes.create_string_buffer and ctypes.c_char_p are all can change python's str to c_char_p
@@ -398,7 +400,7 @@ class LocalUser:
398
400
  def unsubscribe_audio(self, user_id):
399
401
  #validity check
400
402
  if user_id is None:
401
- return -1
403
+ return -1000
402
404
  uid_str = user_id.encode('utf-8')
403
405
  ret = agora_local_user_unsubscribe_audio(self.user_handle, ctypes.c_char_p(uid_str))
404
406
  if ret < 0:
@@ -517,7 +519,7 @@ class LocalUser:
517
519
 
518
520
  def subscribe_video(self, user_id, options: VideoSubscriptionOptions):
519
521
  if user_id is None:
520
- return -1
522
+ return -1000
521
523
  uid_str = user_id.encode('utf-8')
522
524
 
523
525
 
@@ -540,7 +542,7 @@ class LocalUser:
540
542
 
541
543
  def unsubscribe_video(self, user_id):
542
544
  if user_id is None:
543
- return -1
545
+ return -1000
544
546
  uid_str = user_id.encode('utf-8')
545
547
  ret = agora_local_user_unsubscribe_video(self.user_handle, ctypes.c_char_p(uid_str))
546
548
  if ret < 0:
@@ -628,3 +630,16 @@ class LocalUser:
628
630
  ret = self.connection._set_apm_filter_properties(remote_audio_track_handle, user_id_str)
629
631
  print(f"**********LocalUser _set_apm_filter_properties: {ret}")
630
632
  return ret
633
+ pass
634
+ def _send_intra_request(self, remote_uid: str) -> int:
635
+ #validity check
636
+ if remote_uid is None:
637
+ return -1000
638
+ if self.user_handle is None:
639
+ return -1001
640
+ uid_str = remote_uid.encode('utf-8')
641
+ ret = agora_local_user_send_intra_request(self.user_handle, ctypes.c_char_p(uid_str))
642
+ if ret < 0:
643
+ logger.error("Failed to send intra request")
644
+ return ret
645
+ pass
@@ -83,6 +83,13 @@ agora_local_user_unregister_capabilities_observer = agora_lib.agora_local_user_u
83
83
  agora_local_user_unregister_capabilities_observer.restype = AGORA_API_C_INT
84
84
  agora_local_user_unregister_capabilities_observer.argtypes = [AGORA_HANDLE, AGORA_HANDLE]
85
85
 
86
+ agora_local_audio_track_set_total_extra_send_ms = agora_lib.agora_local_audio_track_set_total_extra_send_ms
87
+ agora_local_audio_track_set_total_extra_send_ms.restype = AGORA_API_C_INT
88
+ agora_local_audio_track_set_total_extra_send_ms.argtypes = [AGORA_HANDLE, ctypes.c_uint64]
89
+
90
+ #global variable
91
+ _is_deliver_mute_data_has_set: bool = False
92
+
86
93
  class RTCConnection:
87
94
  def __init__(self, service: AgoraService, conn_config: RTCConnConfig, publish_config: RtcConnectionPublishConfig) -> None:
88
95
  self.conn_handle = None
@@ -90,6 +97,7 @@ class RTCConnection:
90
97
  self.local_user = None
91
98
  self.rtc_engine = service
92
99
  self._con_observer = None
100
+ self._agora_parameter = None
93
101
  #1 create conn_handle
94
102
  self.conn_handle = agora_rtc_conn_create(self.rtc_engine.service_handle, ctypes.byref(RTCConnConfigInner.create(conn_config)))
95
103
  if self.conn_handle is None:
@@ -98,6 +106,7 @@ class RTCConnection:
98
106
  self.local_user_handle = agora_rtc_conn_get_local_user(self.conn_handle)
99
107
  if self.local_user_handle:
100
108
  self.local_user = LocalUser(self.local_user_handle, self)
109
+ self._agora_parameter = self._init_agora_parameter()
101
110
  #keep publish_config
102
111
  self.publish_config = publish_config
103
112
  #and prepare track and sender for publish
@@ -107,6 +116,9 @@ class RTCConnection:
107
116
  self._audio_encoded_sender = None
108
117
  self._video_sender = None
109
118
  self._video_encoded_sender = None
119
+ # for external audio parameters
120
+ self._send_external_audio_parameters = publish_config.send_external_audio_parameters
121
+
110
122
  self._pcm_consume_stats = PcmConsumeStats()
111
123
  self._prepare_publish_track_and_sender()
112
124
  #3 set profile and scenario
@@ -123,12 +135,15 @@ class RTCConnection:
123
135
  self._capabilities_observer_obj = None
124
136
  if self.publish_config.audio_scenario == AudioScenarioType.AUDIO_SCENARIO_AI_SERVER:
125
137
  self._register_capabilities_observer()
138
+ # for external audio parameters
139
+ self._set_send_external_send_frame_speed(self._send_external_audio_parameters)
126
140
 
127
141
  def _prepare_publish_track_and_sender(self)->int:
128
142
  if self.publish_config.is_publish_audio:
129
143
  if self.publish_config.audio_publish_type == AudioPublishType.AUDIO_PUBLISH_TYPE_PCM:
130
144
  self._audio_sender = self.rtc_engine.media_node_factory.create_audio_pcm_data_sender()
131
- self._audio_track = self.rtc_engine._create_custom_audio_track_pcm(self._audio_sender, self.publish_config.audio_scenario)
145
+ is_extra_audio = self._is_support_send_external_audio()
146
+ self._audio_track = self.rtc_engine._create_custom_audio_track_pcm(self._audio_sender, self.publish_config.audio_scenario, is_extra_audio)
132
147
  elif self.publish_config.audio_publish_type == AudioPublishType.AUDIO_PUBLISH_TYPE_ENCODED_PCM:
133
148
  self._audio_encoded_sender = self.rtc_engine.media_node_factory.create_audio_encoded_frame_sender()
134
149
  self._audio_track = self.rtc_engine.create_custom_audio_track_encoded(self._audio_encoded_sender, 1)#mix_mode: MIX_ENABLED = 0, MIX_DISABLED = 1
@@ -203,11 +218,13 @@ class RTCConnection:
203
218
  return ret
204
219
 
205
220
  #
206
- def get_agora_parameter(self):
221
+ def _init_agora_parameter(self):
207
222
  agora_parameter = agora_rtc_conn_get_agora_parameter(self.conn_handle)
208
223
  if not agora_parameter:
209
224
  return None
210
225
  return AgoraParameter(agora_parameter)
226
+ def get_agora_parameter(self):
227
+ return self._agora_parameter
211
228
 
212
229
  #
213
230
 
@@ -397,6 +414,11 @@ class RTCConnection:
397
414
  frame.samples_per_channel = readLen // (channels * 2)
398
415
  frame.present_time_ms = start_pts
399
416
 
417
+ #check if a new round or not. if new round should call _set_total_extra_send_ms()
418
+ is_new_round = self._pcm_consume_stats.is_new_round()
419
+ if is_new_round:
420
+ self._set_total_extra_send_ms()
421
+
400
422
  ret = self._audio_sender.send_audio_pcm_data(frame)
401
423
  self._pcm_consume_stats.add_pcm_data(readLen, sample_rate, channels)
402
424
  return ret
@@ -431,8 +453,9 @@ class RTCConnection:
431
453
 
432
454
  updated_track = None
433
455
  delayed_del_track = None
456
+ is_extra_audio = False
434
457
  if self._audio_sender:
435
- updated_track = self.rtc_engine._create_custom_audio_track_pcm(self._audio_sender, scenario)
458
+ updated_track = self.rtc_engine._create_custom_audio_track_pcm(self._audio_sender, scenario, is_extra_audio)
436
459
  elif self._audio_encoded_sender:
437
460
  updated_track = self.rtc_engine.create_custom_audio_track_encoded(self._audio_encoded_sender, scenario)
438
461
 
@@ -440,6 +463,8 @@ class RTCConnection:
440
463
  delayed_del_track = self._audio_track
441
464
  self._audio_track = updated_track
442
465
  self._audio_track.set_enabled(True)
466
+ self._audio_track.set_send_delay_ms(10)
467
+ self._audio_track.set_max_buffer_audio_frame_number(100000)
443
468
 
444
469
  if delayed_del_track:
445
470
  delayed_del_track.release()
@@ -467,10 +492,10 @@ class RTCConnection:
467
492
  item_index = 0
468
493
  for cap in capabilities:
469
494
  item_index = 0
470
- print(f"Capability[{index}] - Type: {cap.capability_type}")
495
+ logger.debug(f"Capability[{index}] - Type: {cap.capability_type}")
471
496
  index += 1
472
497
  for item in cap.item_map.item:
473
- print(f"Item[{item_index}] - ID: {item.id}, Name: {item.name}")
498
+ logger.debug(f"Item[{item_index}] - ID: {item.id}, Name: {item.name}")
474
499
  item_index += 1
475
500
  if cap.capability_type == 19 and item.name and item.name.upper() == "SUPPORT":
476
501
  fallback_scenario = False
@@ -541,5 +566,64 @@ class RTCConnection:
541
566
  print(f"**********APM: to enable apm_dump, error: {ret}")
542
567
  return ret
543
568
 
569
+ def _set_send_external_send_frame_speed(self, send_external_audio_parameters: SendExternalAudioParameters)->int:
570
+ ret = -1000
571
+ if send_external_audio_parameters == None or send_external_audio_parameters.enabled == False or send_external_audio_parameters.send_ms <= 0 or send_external_audio_parameters.send_speed <= 1:
572
+ return -1001
573
+ speed = send_external_audio_parameters.send_speed
574
+ if speed < 1:
575
+ speed = 1
576
+ if speed > 5:
577
+ speed = 5
578
+ #set send speed for fake adm to connection level
579
+ params = '{"che.audio.extra_send_frames_per_interval_for_fake_adm": %d}' % speed
580
+
581
+ if (self._agora_parameter is None):
582
+ return -1002
583
+ ret = self._agora_parameter.set_parameters(params)
584
+
544
585
 
586
+ # set deliver mute data for fake adm to service level and only once
587
+ self._set_deliver_mute_data_for_fake_adm(send_external_audio_parameters.deliver_mute_data_for_fake_adm)
588
+
589
+ return ret
590
+ pass
591
+
592
+ def _set_deliver_mute_data_for_fake_adm(self, deliver_mute_data_for_fake_adm: bool)->int:
593
+ ret = -1000
594
+ global _is_deliver_mute_data_has_set
595
+ if deliver_mute_data_for_fake_adm == False and _is_deliver_mute_data_has_set == False:
596
+ params = '{"che.audio.deliver_mute_data_for_fake_adm": false}'
597
+ rtc_parameter = self.rtc_engine.get_agora_parameter()
598
+ if rtc_parameter is not None:
599
+ ret = rtc_parameter.set_parameters(params)
600
+ _is_deliver_mute_data_has_set = True
601
+ return ret
602
+ def _is_support_send_external_audio(self)->bool:
603
+ ret = False
604
+ if ((self._send_external_audio_parameters is not None)
605
+ and (self._send_external_audio_parameters.enabled == True)
606
+ and (self._send_external_audio_parameters.send_ms > 0)
607
+ and (self._send_external_audio_parameters.send_speed > 1)):
608
+ ret = True
609
+ return ret
610
+ pass
611
+ #only valid after call this api
612
+ #and default call before each round
613
+ def _set_total_extra_send_ms(self)->int:
614
+ is_support = self._is_support_send_external_audio()
615
+ if is_support == False:
616
+ return 0
617
+ send_ms = self._send_external_audio_parameters.send_ms
618
+ ret = agora_local_audio_track_set_total_extra_send_ms(self._audio_track.track_handle, ctypes.c_uint64(send_ms))
619
+
620
+ return ret
621
+ pass
622
+ def send_intra_request(self, remote_uid: str) -> int:
623
+ ret = -1000
624
+ if self.local_user is None:
625
+ return -1001
626
+ ret = self.local_user._send_intra_request(remote_uid)
627
+ return ret
628
+ pass
545
629
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: agora_python_server_sdk
3
- Version: 2.4.0
3
+ Version: 2.4.2
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
@@ -38,7 +38,7 @@ Description-Content-Type: text/markdown
38
38
  # Required Operating Systems and Python Versions
39
39
  - Supported Linux versions:
40
40
  - Ubuntu 18.04 LTS and above
41
- - CentOS 7.0 and above
41
+ - CentOS 8.0 and above
42
42
 
43
43
  - Supported Mac versions:
44
44
  - MacOS 13 and above(only for coding and testing)
@@ -57,40 +57,77 @@ pip install agora_python_server_sdk
57
57
  - Download and unzip [test_data.zip](https://download.agora.io/demo/test/test_data_202408221437.zip) to the Agora-Python-Server-SDK directory.
58
58
 
59
59
  ## Executing Test Script
60
- or linux os, should set env to :/site_packages/agora/agora_sdk/, like:
60
+ for linux os, should set env to :/site_packages/agora/agora_sdk/, like:
61
61
  export LD_LIBRARY_PATH=/site_packages/agora/agora_sdk/
62
62
  ```
63
63
  python agora_rtc/examples/example_audio_pcm_send.py --appId=xxx --channelId=xxx --userId=xxx --audioFile=./test_data/demo.pcm --sampleRate=16000 --numOfChannels=1
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
+ ## 2025.12.29 Release Version 2.4.2
68
+
69
+ - Added **incremental send mode** support.
70
+ - New `connection::send_intra_request` API, allowing you to initiate an intra request to remote users and trigger them to send a key frame.
71
+
72
+ #### Example usage of incremental send mode
73
+
74
+ ```python
75
+ # Configure incremental send parameters
76
+ publish_config.send_external_audio_parameters = SendExternalAudioParameters(
77
+ enabled=True,
78
+ send_ms=2000,
79
+ send_speed=2,
80
+ deliver_mute_data_for_fake_adm=False
81
+ )
82
+
83
+ # Create an RTC connection
84
+ connection = agora_service.create_rtc_connection(con_config, publish_config)
85
+ ```
86
+
87
+
88
+
89
+ ## 2025.12.17 Release 2.4.1
90
+
91
+ - Updated RTC SDK to version 154.
92
+ - Added support for VAD (Voice Activity Detection) for external audio sources, including:
93
+ - **Background voice removal**
94
+ - **Noise suppression**
95
+ - **Echo cancellation**
96
+ - **Automatic gain control**
97
+ - **Other 3A algorithms**
98
+ All supported via the `external_Audio_Processor`.
99
+ - Modified LocalUser return values for clearer SDK error code distinction.
100
+ - Added `example_external_Audio_Processor.py` to demonstrate external audio data processing.
101
+
102
+ ---
103
+
104
+ ## 2025.11.18 Release 2.4.0
105
+
106
+ - Updated SDK to APM version: `4.4.32/1025`
107
+ - Added configuration support to enable/disable APM.
108
+ - Updated methods in `setup.py` and `__init__.py`, including version/URL and MD5 processing.
109
+ - Completed main code pipeline (**further testing recommended**).
110
+ - Added RTM support: one SDK now supports both RTC and RTM.
111
+
112
+ **To-Do Items:**
113
+ - [x] Add VAD algorithm update
114
+ - [x] Add vad_dump modifications
115
+ - [x] Add APM algorithm VAD switch support
116
+ - [x] Add VAD configuration parameter setting
117
+ - [x] Download and check for MD5 mismatch on every download
118
+
119
+ **Notes:**
120
+ - **APM features** include server-side echo cancellation, noise suppression, automatic gain control, background voice removal, and more.
121
+ - Typically, AEC/AINS/AGC, etc., are handled on the client; server-side activation is only necessary for specific requirements.
122
+ - If you need to enable APM, please contact Agora technical support.
123
+
124
+ **Usage Examples:**
125
+ - [RTC usage example](https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK/tree/main/agora_rtc/examples)
126
+ - [RTM usage example](https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK/tree/main/agora_rtc/rtm_examples)
86
127
  # 2025.11.07 release 2.3.3
87
128
  -- update: to support rtm
88
129
  -- adjust sdk's directory structure
89
130
  -- change requests to urllib
90
- # 2025.10.23 release 2.3.2: support rtc and rtm in one package
91
- -- update: to support rtm.can support both rtc and rtm in one package.
92
- -- adjust sdk's directory structure
93
- -- update rtc sdk
94
131
  # 2025.10.09 release 2.3.1
95
132
  -- update arm64 rtc sdk:Fixed a JNI referencing issue in the previous arm64 build. This issue only outputs logs to the console and does not affect functionality.
96
133
 
@@ -12,6 +12,7 @@ agora/rtc/audio_frame_observer.py
12
12
  agora/rtc/audio_pcm_data_sender.py
13
13
  agora/rtc/audio_sessionctrl.py
14
14
  agora/rtc/audio_vad_manager.py
15
+ agora/rtc/external_audio_processor.py
15
16
  agora/rtc/local_audio_track.py
16
17
  agora/rtc/local_user.py
17
18
  agora/rtc/local_user_observer.py
@@ -84,6 +84,16 @@ class CustomInstallCommand(install):
84
84
  #20251110 Fusion version: with apm filter
85
85
  mac_sdk="https://download.agora.io/sdk/release/agora_sdk_mac_v4.4.30_25869_FULL_20251030_1836_953684-aed.zip"
86
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
+ #date:20251124 update sdk for fix the issue of auto load so and auto load ains resource
88
+ url = "https://download.agora.io/sdk/release/agora_rtc_sdk_x86_64-linux-gnu-Agora_Native_SDK_for_Linux_x64_zhourui_26895_SERVER_20251121_1628_987405_20251021_1427-3a.zip"
89
+ mac_sdk="https://download.agora.io/sdk/release/agora_sdk_mac_Agora_Native_SDK_for_Mac_zhourui_26101_FULL_20251121_2135_987705_20251021_1427-3a.zip"
90
+ #added local track apm filter
91
+ #date:20251217 for incremental sending mode support
92
+ url="https://download.agora.io/sdk/release/agora_rtc_sdk_x86_64-linux-gnu-v4.4.32.154_26982_SERVER_20251210_1745_994155_20251021_1427-3a.zip"
93
+ mac_sdk="https://download.agora.io/sdk/release/agora_sdk_mac_v4.4.32.154_26308_FULL_20251210_1756_994156_20251021_1427-3a.zip"
94
+
95
+
96
+
87
97
  if sys.platform == 'darwin':
88
98
  url = mac_sdk
89
99
 
@@ -109,11 +119,14 @@ class CustomInstallCommand(install):
109
119
 
110
120
  if os.path.exists(zip_path):
111
121
  os.remove(zip_path)
122
+ #write version url to version.txt ,to replace the md5 check
123
+ with open(os.path.join(agora_service_path, "version.txt"), "w") as f:
124
+ f.write(url)
112
125
 
113
126
 
114
127
  setup(
115
128
  name='agora_python_server_sdk',
116
- version='2.4.0',
129
+ version='2.4.2',
117
130
  description='A Python SDK for Agora Server',
118
131
  long_description=open('README.md').read(),
119
132
  long_description_content_type='text/markdown',