sippy 2.2.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.
- {sippy-2.2.3 → sippy-2.4.0}/MANIFEST.in +1 -0
- {sippy-2.2.3/sippy.egg-info → sippy-2.4.0}/PKG-INFO +5 -1
- sippy-2.4.0/requirements.txt +7 -0
- {sippy-2.2.3 → sippy-2.4.0}/setup.py +6 -3
- {sippy-2.2.3 → sippy-2.4.0}/sippy/B2B/Route.py +75 -22
- {sippy-2.2.3 → sippy-2.4.0}/sippy/B2B/SimpleAPI.py +7 -3
- {sippy-2.2.3 → sippy-2.4.0}/sippy/MyConfigParser.py +7 -3
- sippy-2.4.0/sippy/Rtp/Codecs/G711.py +42 -0
- sippy-2.4.0/sippy/Rtp/Codecs/G722.py +53 -0
- sippy-2.4.0/sippy/Rtp/Codecs/GenCodec.py +18 -0
- sippy-2.4.0/sippy/Rtp/Conf.py +8 -0
- sippy-2.4.0/sippy/Rtp/Core/AudioChunk.py +54 -0
- sippy-2.4.0/sippy/Rtp/EPoint.py +244 -0
- sippy-2.4.0/sippy/Rtp/Handlers.py +14 -0
- sippy-2.4.0/sippy/Rtp/Ingest.py +79 -0
- sippy-2.4.0/sippy/Rtp/OutputWorker.py +204 -0
- sippy-2.4.0/sippy/Rtp/Params.py +104 -0
- sippy-2.4.0/sippy/Rtp/Singletons.py +61 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Client/udp.py +2 -5
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Session/side.py +2 -3
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/session.py +18 -10
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Security/SipNonce.py +26 -12
- sippy-2.4.0/sippy/Security/__init__.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipAuthorization.py +3 -16
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipCSeq.py +10 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipCallId.py +2 -4
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipCiscoGUID.py +3 -6
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipFrom.py +2 -4
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipVia.py +2 -4
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipWWWAuthenticate.py +2 -9
- sippy-2.4.0/sippy/Time/__init__.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/UA.py +2 -5
- sippy-2.4.0/sippy/UI/Auth.py +64 -0
- sippy-2.4.0/sippy/UI/Controller.py +380 -0
- sippy-2.4.0/sippy/UI/Lib.py +248 -0
- sippy-2.4.0/sippy/UI/SystemConfiguration.py +5 -0
- sippy-2.4.0/sippy/UI/__init__.py +0 -0
- sippy-2.4.0/sippy/UI/static/active_calls.js +17 -0
- sippy-2.4.0/sippy/UI/static/config_box.css +29 -0
- sippy-2.4.0/sippy/UI/static/lib.js +146 -0
- sippy-2.4.0/sippy/UI/static/login.css +103 -0
- sippy-2.4.0/sippy/UI/static/ui.css +50 -0
- sippy-2.4.0/sippy/UI/templates/login.html +81 -0
- sippy-2.4.0/sippy/UI/templates/redir2login.html +14 -0
- sippy-2.4.0/sippy/UaStateConnected.py +311 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/UaStateGeneric.py +26 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/UacStateIdle.py +2 -1
- {sippy-2.2.3 → sippy-2.4.0}/sippy/UacStateUpdating.py +32 -25
- sippy-2.4.0/sippy/UasStateGeneric.py +79 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/UasStateIdle.py +2 -1
- sippy-2.4.0/sippy/UasStateProgressing.py +84 -0
- sippy-2.4.0/sippy/UasStateRinging.py +64 -0
- sippy-2.4.0/sippy/UasStateTrying.py +89 -0
- sippy-2.4.0/sippy/UasStateUpdating.py +103 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/b2bua.py +7 -1
- {sippy-2.2.3 → sippy-2.4.0/sippy.egg-info}/PKG-INFO +5 -1
- {sippy-2.2.3 → sippy-2.4.0}/sippy.egg-info/SOURCES.txt +31 -0
- sippy-2.4.0/sippy.egg-info/requires.txt +7 -0
- sippy-2.4.0/tests/__init__.py +0 -0
- sippy-2.4.0/tests/test_HashOracle.py +30 -0
- sippy-2.4.0/tests/test_RtpEPoint.py +61 -0
- sippy-2.4.0/tests/test_RtpParams.py +66 -0
- sippy-2.2.3/requirements.txt +0 -3
- sippy-2.2.3/sippy/UaStateConnected.py +0 -296
- sippy-2.2.3/sippy/UasStateRinging.py +0 -144
- sippy-2.2.3/sippy/UasStateTrying.py +0 -137
- sippy-2.2.3/sippy/UasStateUpdating.py +0 -128
- sippy-2.2.3/sippy.egg-info/requires.txt +0 -3
- {sippy-2.2.3 → sippy-2.4.0}/LICENSE +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/README.md +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/pyproject.toml +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/setup.cfg +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/B2B/States.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/B2B/Transforms.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/B2B/__init__.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/CCEvents.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/CLIManager.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Core/EventDispatcher.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Core/Exceptions.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Core/__init__.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/ESipHeaderCSV.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/ESipHeaderIgnore.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Exceptions/RtpProxyError.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Exceptions/SdpParseError.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Exceptions/SipHandlingError.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Exceptions/SipParseError.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Exceptions/__init__.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/External_command.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/FakeAccounting.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Math/__init__.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Math/recfilter.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/MsgBody.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/MultipartMixBody.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Network_server.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/RadiusAccounting.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/RadiusAuthorisation.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Radius_client.py +0 -0
- {sippy-2.2.3/sippy/Rtp_proxy/Client/Worker → sippy-2.4.0/sippy/Rtp/Codecs}/__init__.py +0 -0
- {sippy-2.2.3/sippy/Security → sippy-2.4.0/sippy/Rtp/Core}/__init__.py +0 -0
- {sippy-2.2.3/sippy/Time → sippy-2.4.0/sippy/Rtp}/__init__.py +0 -0
- {sippy-2.2.3/tests → sippy-2.4.0/sippy/Rtp_proxy/Client/Worker}/__init__.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Client/Worker/external.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Client/Worker/internal.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Client/__init__.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Client/internal.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Client/local.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Client/net.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Client/stream.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Cmd/__init__.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Cmd/sequencer.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Session/__init__.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Session/subcommand.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Session/subcommand_ice.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Session/update.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/Session/webrtc.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/__init__.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/client.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Rtp_proxy/cmd.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SdpBody.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SdpConnecton.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SdpGeneric.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SdpMedia.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SdpMediaDescription.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SdpOrigin.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Signal.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipAddress.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipAddressHF.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipAllow.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipAlso.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipCCDiversion.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipConf.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipContact.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipContentLength.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipContentType.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipDiversion.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipExpires.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipGenericHF.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipHeader.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipLogger.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipMaxForwards.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipMsg.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipNumericHF.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipPAssertedIdentity.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipPath.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipProxyAuthenticate.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipProxyAuthorization.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipRAck.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipRSeq.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipReason.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipRecordRoute.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipReferTo.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipReferredBy.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipRegistrationAgent.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipReplaces.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipRequest.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipResponse.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipRoute.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipServer.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipSupported.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipTo.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipTransactionManager.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipURL.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipUserAgent.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/SipWarning.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/StatefulProxy.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Time/MonoTime.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Time/Timeout.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Time/clock_dtime.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/UaStateDead.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/UaStateDisconnected.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/UaStateFailed.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/UacStateCancelling.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/UacStateRinging.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/UacStateTrying.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Udp_server.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/Wss_server.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/XMPP_server.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/__init__.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/b2bua_simple.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/dictionary +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy/misc.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy.egg-info/dependency_links.txt +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy.egg-info/entry_points.txt +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/sippy.egg-info/top_level.txt +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/tests/test_B2BRoute.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/tests/test_B2BTransforms.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/tests/test_Multipart.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/tests/test_SdbBody.py +0 -0
- {sippy-2.2.3 → sippy-2.4.0}/tests/test_Udp_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sippy
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: RFC3261 SIP Stack and Back-to-Back User Agent (B2BUA)
|
|
5
5
|
Home-page: http://www.b2bua.org/
|
|
6
6
|
Author: Sippy Software, Inc.
|
|
@@ -13,8 +13,12 @@ Classifier: Programming Language :: Python
|
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Requires-Dist: ElPeriodic>=1.1
|
|
16
|
+
Requires-Dist: rtpsynth>=1.3.0
|
|
17
|
+
Requires-Dist: g722>=1.2.3
|
|
16
18
|
Requires-Dist: pycryptodome
|
|
17
19
|
Requires-Dist: websockets
|
|
20
|
+
Requires-Dist: flask
|
|
21
|
+
Requires-Dist: flask-login
|
|
18
22
|
Dynamic: author
|
|
19
23
|
Dynamic: author-email
|
|
20
24
|
Dynamic: classifier
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
from setuptools import setup, find_packages
|
|
3
3
|
|
|
4
|
-
requirements = [
|
|
4
|
+
requirements = [
|
|
5
|
+
line.strip()
|
|
6
|
+
for line in open("requirements.txt", "r", encoding="utf-8").readlines()
|
|
7
|
+
if line.strip() and not line.lstrip().startswith("#")
|
|
8
|
+
]
|
|
5
9
|
|
|
6
10
|
with open("README.md", "r") as fh:
|
|
7
11
|
long_description = fh.read()
|
|
8
12
|
|
|
9
13
|
setup(
|
|
10
14
|
name = "sippy",
|
|
11
|
-
version = "2.
|
|
15
|
+
version = "2.4.0",
|
|
12
16
|
packages = find_packages(),
|
|
13
17
|
|
|
14
18
|
install_requires = requirements,
|
|
15
19
|
include_package_data=True,
|
|
16
|
-
test_suite = 'tests',
|
|
17
20
|
|
|
18
21
|
entry_points = {
|
|
19
22
|
'console_scripts': [
|
|
@@ -40,32 +40,69 @@ DST_SIP_UA = 'sip-ua'
|
|
|
40
40
|
DST_WSS_UA = 'wss-ua'
|
|
41
41
|
|
|
42
42
|
class B2BRoute(object):
|
|
43
|
+
crt_set = False
|
|
44
|
+
params = None
|
|
45
|
+
extra_headers = None
|
|
43
46
|
rnum = None
|
|
47
|
+
_ui_cats = {'auth': 'Outbound Digest Authentication', 'net': 'Network', 'lim': 'Limits',
|
|
48
|
+
'num': 'Calling / Called Numbers', 'acct': 'Accounting'}
|
|
49
|
+
_ui_hints = {}
|
|
50
|
+
# _ui_hints['rnum'] = {'hidden': True}
|
|
44
51
|
addrinfo = None
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
passw = None
|
|
52
|
+
# _ui_hints['addrinfo'] = {'readonly': True, 'category': _ui_cats['net'], 'hidden': True}
|
|
53
|
+
hostonly:str = None
|
|
54
|
+
# _ui_hints['hostonly'] = {'readonly': True, 'category': _ui_cats['net'], 'hidden': True}
|
|
55
|
+
ainfo = None
|
|
56
|
+
# _ui_hints['ainfo'] = {'readonly': True, 'category': _ui_cats['net'], 'hidden': True}
|
|
57
|
+
hostport:str = None
|
|
58
|
+
_ui_hints['hostport'] = {'name': 'Destination', 'description': 'Destination host:port',
|
|
59
|
+
'category': _ui_cats['net']}
|
|
60
|
+
user:str = None
|
|
61
|
+
_ui_hints['user'] = {'name': 'SIP Username', 'category': _ui_cats['auth'],
|
|
62
|
+
'active': lambda o: not o.pass_auth}
|
|
63
|
+
passw:str = None
|
|
64
|
+
_ui_hints['passw'] = {'name': 'SIP Password', 'category': _ui_cats['auth'], 'password': True,
|
|
65
|
+
'active': lambda o: not o.pass_auth}
|
|
57
66
|
pass_auth = True
|
|
58
|
-
|
|
67
|
+
_ui_hints['pass_auth'] = {'name': 'Mode', 'category': _ui_cats['auth'],
|
|
68
|
+
'values': {False: 'Normal ("UAC")', True: 'Pass-through ("Proxy")'},}
|
|
69
|
+
_cld:str = None
|
|
70
|
+
_ui_hints['cld'] = {'name': 'Called Party (CLD)', 'description': 'Called party number',
|
|
71
|
+
'category': _ui_cats['num'], 'type': str}
|
|
72
|
+
cld_set = False
|
|
73
|
+
_ui_hints['cld_set'] = {'name': 'CLD Mode', 'description': 'Controls outbound CLD',
|
|
74
|
+
'category': _ui_cats['num'], 'active': lambda o: not o.cld,
|
|
75
|
+
'values': lambda o: {True: 'Erase', False: 'Pass-through'}
|
|
76
|
+
if not o.cld else {True: 'Replace'}}
|
|
77
|
+
_cli:str = None
|
|
78
|
+
_ui_hints['cli'] = {'name': 'Calling Party (CLI)', 'description': 'Caller number',
|
|
79
|
+
'category': _ui_cats['num'], 'type': str}
|
|
59
80
|
cli_set = False
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
81
|
+
_ui_hints['cli_set'] = {'name': 'CLI Mode', 'description': 'Controls outbound CLI', 'category':
|
|
82
|
+
_ui_cats['num'], 'active': lambda o: not o.cli,
|
|
83
|
+
'values': lambda o: {True: 'Erase', False: 'Pass-through'}
|
|
84
|
+
if not o.cli else {True: 'Replace'}}
|
|
85
|
+
credit_time:float = None
|
|
86
|
+
_ui_hints['credit_time'] = {'name': 'Credit Time', 'description': 'Allowed call duration, in seconds',
|
|
87
|
+
'category': _ui_cats['lim']}
|
|
88
|
+
expires:float = None
|
|
89
|
+
_ui_hints['expires'] = {'name': 'Route Expiry', 'description': 'Time to expire the route, in seconds',
|
|
90
|
+
'category': _ui_cats['lim']}
|
|
91
|
+
no_progress_expires:float = None
|
|
92
|
+
_ui_hints['no_progress_expires'] = {'name': 'No Progress Timeout', 'category': _ui_cats['lim'],
|
|
93
|
+
'description': 'Time to expire the route if no 180 or 183 provisional response received, in seconds'}
|
|
94
|
+
no_reply_expires:float = None
|
|
95
|
+
_ui_hints['no_reply_expires'] = {'name': 'No Reply Timeout', 'category': _ui_cats['lim'],
|
|
96
|
+
'description': 'Time to expire the route if no provisional response received, in seconds'}
|
|
97
|
+
forward_on_fail = False
|
|
98
|
+
_ui_hints['forward_on_fail'] = {'name': 'Failed Calls', 'category': _ui_cats['acct'],
|
|
99
|
+
'description': 'Emit accounting on SIP failures'}
|
|
63
100
|
|
|
64
101
|
def __init__(self, sroute = None, cself = None):
|
|
65
102
|
if cself != None:
|
|
66
103
|
self.rnum = cself.rnum
|
|
67
104
|
self.addrinfo = cself.addrinfo
|
|
68
|
-
self.
|
|
105
|
+
self._cld = cself.cld
|
|
69
106
|
self.cld_set = cself.cld_set
|
|
70
107
|
self.hostport = cself.hostport
|
|
71
108
|
self.hostonly = cself.hostonly
|
|
@@ -78,7 +115,7 @@ class B2BRoute(object):
|
|
|
78
115
|
self.user = cself.user
|
|
79
116
|
self.passw = cself.passw
|
|
80
117
|
self.pass_auth = cself.pass_auth
|
|
81
|
-
self.
|
|
118
|
+
self._cli = cself.cli
|
|
82
119
|
self.cli_set = cself.cli_set
|
|
83
120
|
self.params = dict(cself.params)
|
|
84
121
|
self.ainfo = cself.ainfo
|
|
@@ -93,7 +130,6 @@ class B2BRoute(object):
|
|
|
93
130
|
# as opposed to the Routing:host, which means that CLD should be obtained
|
|
94
131
|
# from the incoming call leg.
|
|
95
132
|
self.cld = None
|
|
96
|
-
self.cld_set = True
|
|
97
133
|
else:
|
|
98
134
|
self.hostport = route[0]
|
|
99
135
|
if not self.hostport.startswith('['):
|
|
@@ -147,7 +183,6 @@ class B2BRoute(object):
|
|
|
147
183
|
self.cli = v
|
|
148
184
|
if len(self.cli) == 0:
|
|
149
185
|
self.cli = None
|
|
150
|
-
self.cli_set = True
|
|
151
186
|
elif a == 'cnam':
|
|
152
187
|
caller_name = unquote(v)
|
|
153
188
|
if len(caller_name) == 0:
|
|
@@ -175,9 +210,9 @@ class B2BRoute(object):
|
|
|
175
210
|
pass_headers, max_credit_time):
|
|
176
211
|
self.rnum = rnum
|
|
177
212
|
if not self.cld_set:
|
|
178
|
-
self.
|
|
213
|
+
self._cld = default_cld
|
|
179
214
|
if not self.cli_set:
|
|
180
|
-
self.
|
|
215
|
+
self._cli = default_cli
|
|
181
216
|
if not self.crt_set:
|
|
182
217
|
self.crt_set = default_credit_time
|
|
183
218
|
if 'gt' in self.params:
|
|
@@ -210,3 +245,21 @@ class B2BRoute(object):
|
|
|
210
245
|
if af == AF_INET6:
|
|
211
246
|
return ((('[%s]' % amatch[0], amatch[1]), same_af))
|
|
212
247
|
return (((amatch[0], amatch[1]), same_af))
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def cli(self):
|
|
251
|
+
return self._cli
|
|
252
|
+
|
|
253
|
+
@cli.setter
|
|
254
|
+
def cli(self, value):
|
|
255
|
+
self._cli = value
|
|
256
|
+
self.cli_set = True
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def cld(self):
|
|
260
|
+
return self._cld
|
|
261
|
+
|
|
262
|
+
@cld.setter
|
|
263
|
+
def cld(self, value):
|
|
264
|
+
self._cld = value
|
|
265
|
+
self.cld_set = True
|
|
@@ -116,10 +116,11 @@ class B2BSimpleAPI(CLIConnectionManager):
|
|
|
116
116
|
clim.send('OK\n')
|
|
117
117
|
return False
|
|
118
118
|
if cmd == 'r':
|
|
119
|
-
if len(args)
|
|
120
|
-
clim.send('ERROR: syntax error: r
|
|
119
|
+
if len(args) not in (1, 2):
|
|
120
|
+
clim.send('ERROR: syntax error: r <id> [<media-index>]\n')
|
|
121
121
|
return False
|
|
122
122
|
idx = int(args[0])
|
|
123
|
+
media_index = int(args[1]) if len(args) == 2 else 0
|
|
123
124
|
dlist = [x for x in ccm.ccmap if x.id == idx]
|
|
124
125
|
if len(dlist) == 0:
|
|
125
126
|
clim.send('ERROR: no call with id of %d has been found\n' % idx)
|
|
@@ -128,9 +129,12 @@ class B2BSimpleAPI(CLIConnectionManager):
|
|
|
128
129
|
if not cc.proxied:
|
|
129
130
|
continue
|
|
130
131
|
if cc.state == CCStateConnected:
|
|
131
|
-
cc.disconnect(MonoTime().getOffsetCopy(-60), origin = 'media_timeout'
|
|
132
|
+
cc.disconnect(MonoTime().getOffsetCopy(-60), origin = 'media_timeout',
|
|
133
|
+
media_index = media_index)
|
|
132
134
|
continue
|
|
133
135
|
if cc.state == CCStateARComplete:
|
|
136
|
+
if cc.rtp_proxy_session is not None:
|
|
137
|
+
cc.rtp_proxy_session.media_timeout_index = media_index
|
|
134
138
|
cc.uaO.disconnect(MonoTime().getOffsetCopy(-60), origin = 'media_timeout')
|
|
135
139
|
continue
|
|
136
140
|
clim.send('OK\n')
|
|
@@ -104,9 +104,13 @@ SUPPORTED_OPTIONS = { \
|
|
|
104
104
|
'"host:port:cert_file:key_file", where "cert_file" ' \
|
|
105
105
|
'/ "key_file" are paths to the TLS certificate ' \
|
|
106
106
|
'and key file respectively in the X.509 PEM format'),
|
|
107
|
-
'pre_auth_proc':
|
|
108
|
-
|
|
109
|
-
'xmpp_b2bua_id': ('I', 'ID passed to the XMPP socket server')
|
|
107
|
+
'pre_auth_proc': ('S', 'internal routine to be executed before authentication '\
|
|
108
|
+
'is being processed. E.g. "HDR2Xattrs[X-foo-hdr]."'), \
|
|
109
|
+
'xmpp_b2bua_id': ('I', 'ID passed to the XMPP socket server'), \
|
|
110
|
+
'ui': ('B', 'enable Web management UI'), \
|
|
111
|
+
'uiparams': ('S', 'Semicolon-separated list of UI parameters corresponding to ' \
|
|
112
|
+
'the flask\'s run() routine'), \
|
|
113
|
+
}
|
|
110
114
|
|
|
111
115
|
class MyConfigParser(RawConfigParser):
|
|
112
116
|
_default_section = None
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from array import array
|
|
2
|
+
|
|
3
|
+
from rtpsynth.RtpUtils import linear2ulaw, ulaw2linear
|
|
4
|
+
|
|
5
|
+
from ..Core.AudioChunk import AudioChunk, pcm16_from
|
|
6
|
+
from .GenCodec import GenCodec
|
|
7
|
+
|
|
8
|
+
class G711Codec(GenCodec):
|
|
9
|
+
ptype = 0 # G.711u
|
|
10
|
+
ename = 'PCMU'
|
|
11
|
+
chunk_cls = AudioChunk
|
|
12
|
+
|
|
13
|
+
def encode(self, chunk):
|
|
14
|
+
pcm16 = pcm16_from(chunk.audio)
|
|
15
|
+
return linear2ulaw(pcm16)
|
|
16
|
+
|
|
17
|
+
def make_chunk(self, pcm16: array, srate: int):
|
|
18
|
+
return self.chunk_cls(pcm16, srate)
|
|
19
|
+
|
|
20
|
+
def decode(self, ulaw_bytes:bytes, resample:bool=True, sample_rate:int=None):
|
|
21
|
+
pcm16 = ulaw2linear(ulaw_bytes)
|
|
22
|
+
if sample_rate is None:
|
|
23
|
+
sample_rate = self.out_srate
|
|
24
|
+
chunk = self.make_chunk(pcm16, self.srate)
|
|
25
|
+
if resample and sample_rate != self.srate:
|
|
26
|
+
chunk.resample(sample_rate)
|
|
27
|
+
return chunk
|
|
28
|
+
|
|
29
|
+
def e2d_frames(self, enframes:int, out_srate:int=None):
|
|
30
|
+
if out_srate is None:
|
|
31
|
+
out_srate = self.out_srate
|
|
32
|
+
assert out_srate % self.srate == 0
|
|
33
|
+
return enframes * out_srate // self.srate
|
|
34
|
+
|
|
35
|
+
def d2e_frames(self, dnframes:int, in_srate:int=None):
|
|
36
|
+
if in_srate is None:
|
|
37
|
+
in_srate = self.out_srate
|
|
38
|
+
assert in_srate % self.srate == 0
|
|
39
|
+
return dnframes * self.srate // in_srate
|
|
40
|
+
|
|
41
|
+
def silence(self, nframes:int):
|
|
42
|
+
return b'\xff' * nframes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from array import array
|
|
2
|
+
|
|
3
|
+
from G722 import G722
|
|
4
|
+
|
|
5
|
+
from ..Core.AudioChunk import AudioChunk, pcm16_from
|
|
6
|
+
from .GenCodec import GenCodec
|
|
7
|
+
|
|
8
|
+
class G722Codec(GenCodec):
|
|
9
|
+
codec:G722
|
|
10
|
+
srate:int = 8000
|
|
11
|
+
default_br:int = 64000
|
|
12
|
+
ptype:int = 9 # G.722
|
|
13
|
+
ename:str = 'G722' # encoding name
|
|
14
|
+
chunk_cls = AudioChunk
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
super().__init__()
|
|
18
|
+
self.codec = G722(self.srate, self.default_br, use_numpy=False)
|
|
19
|
+
|
|
20
|
+
def encode(self, chunk):
|
|
21
|
+
pcm16 = pcm16_from(chunk.audio)
|
|
22
|
+
return self.codec.encode(pcm16)
|
|
23
|
+
|
|
24
|
+
def make_chunk(self, pcm16: array, srate: int):
|
|
25
|
+
return self.chunk_cls(pcm16, srate)
|
|
26
|
+
|
|
27
|
+
def decode(self, audio_enc:bytes, resample:bool=True, sample_rate:int=None):
|
|
28
|
+
pcm16 = array('h', self.codec.decode(audio_enc))
|
|
29
|
+
if sample_rate is None:
|
|
30
|
+
sample_rate = self.out_srate
|
|
31
|
+
chunk = self.make_chunk(pcm16, self.srate)
|
|
32
|
+
if resample and sample_rate != self.srate:
|
|
33
|
+
chunk.resample(sample_rate)
|
|
34
|
+
return chunk
|
|
35
|
+
|
|
36
|
+
def silence(self, nframes:int):
|
|
37
|
+
pcm16 = array('h', [0]) * self.e2d_frames(nframes)
|
|
38
|
+
return self.encode(pcm16)
|
|
39
|
+
|
|
40
|
+
def e2d_frames(self, enframes:int, out_srate:int=None):
|
|
41
|
+
#assert out_srate % self.srate == 0
|
|
42
|
+
if out_srate is None:
|
|
43
|
+
out_srate = self.out_srate
|
|
44
|
+
return enframes * (1 if self.srate == 8000 else 2) * out_srate // self.srate
|
|
45
|
+
|
|
46
|
+
def d2e_frames(self, dnframes:int, in_srate:int=None):
|
|
47
|
+
#assert in_srate % self.srate == 0
|
|
48
|
+
if in_srate is None:
|
|
49
|
+
in_srate = self.out_srate
|
|
50
|
+
return dnframes * self.srate // ((1 if self.srate == 8000 else 2) * in_srate)
|
|
51
|
+
|
|
52
|
+
def e2t(self, frames:int) -> float:
|
|
53
|
+
return (frames * 8) / self.default_br
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class GenCodec():
|
|
2
|
+
srate:int = 8000 # sample rate
|
|
3
|
+
out_srate:int # sample rate (output)
|
|
4
|
+
crate:int = 8000 # clock rate
|
|
5
|
+
ptype:int # payload type
|
|
6
|
+
ename:str # encoding name
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
assert self.ptype is not None and self.ename is not None
|
|
10
|
+
self.out_srate = self.srate
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def rtpmap(cls):
|
|
14
|
+
assert all(hasattr(cls, attr) for attr in ('ptype', 'ename'))
|
|
15
|
+
return f'rtpmap:{cls.ptype} {cls.ename}/{cls.crate}'
|
|
16
|
+
|
|
17
|
+
def e2t(self, frames:int) -> float:
|
|
18
|
+
return frames / self.srate
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from array import array
|
|
4
|
+
from typing import Union
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from rtpsynth.RtpUtils import resample_linear
|
|
8
|
+
|
|
9
|
+
PCM16Like = Union[array, bytes, bytearray, memoryview]
|
|
10
|
+
|
|
11
|
+
def pcm16_from(data: PCM16Like) -> array:
|
|
12
|
+
if isinstance(data, array):
|
|
13
|
+
if data.typecode != 'h':
|
|
14
|
+
raise TypeError("PCM16 array must have typecode 'h'")
|
|
15
|
+
return data
|
|
16
|
+
else:
|
|
17
|
+
mv = memoryview(data)
|
|
18
|
+
if mv.format != 'h':
|
|
19
|
+
mv = mv.cast('h')
|
|
20
|
+
pcm = array('h', mv)
|
|
21
|
+
if sys.byteorder == 'big':
|
|
22
|
+
pcm.byteswap()
|
|
23
|
+
return pcm
|
|
24
|
+
|
|
25
|
+
def pcm16_to_bytes(pcm: array) -> bytes:
|
|
26
|
+
if sys.byteorder == 'big':
|
|
27
|
+
pcm = array('h', pcm)
|
|
28
|
+
pcm.byteswap()
|
|
29
|
+
return pcm.tobytes()
|
|
30
|
+
|
|
31
|
+
class AudioChunk():
|
|
32
|
+
debug: bool = False
|
|
33
|
+
samplerate: int
|
|
34
|
+
audio: array
|
|
35
|
+
rtime: float | None = None
|
|
36
|
+
def __init__(self, audio: PCM16Like, samplerate:int):
|
|
37
|
+
self.audio = pcm16_from(audio)
|
|
38
|
+
self.samplerate = samplerate
|
|
39
|
+
|
|
40
|
+
def resample(self, sample_rate:int):
|
|
41
|
+
assert sample_rate != self.samplerate
|
|
42
|
+
self.audio = resample_linear(self.audio, self.samplerate, sample_rate)
|
|
43
|
+
self.samplerate = sample_rate
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
def duration(self):
|
|
47
|
+
return len(self.audio) / self.samplerate
|
|
48
|
+
|
|
49
|
+
def to_bytes(self) -> bytes:
|
|
50
|
+
return pcm16_to_bytes(self.audio)
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def nframes(self) -> int:
|
|
54
|
+
return len(self.audio)
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
from typing import Optional, Tuple
|
|
2
|
+
from uuid import uuid4, UUID
|
|
3
|
+
from threading import Lock
|
|
4
|
+
import errno
|
|
5
|
+
import weakref
|
|
6
|
+
|
|
7
|
+
from rtpsynth.RtpServer import RtpQueueFullError, RtpServer
|
|
8
|
+
from sippy.Time.MonoTime import MonoTime
|
|
9
|
+
|
|
10
|
+
from .Core.AudioChunk import AudioChunk
|
|
11
|
+
from .Handlers import RTPHandlers
|
|
12
|
+
from .Params import RTPParams
|
|
13
|
+
from .Conf import RTPConf
|
|
14
|
+
from .Singletons import acquire_rtp_server, release_rtp_server
|
|
15
|
+
|
|
16
|
+
class RTPEPoint():
|
|
17
|
+
debug: bool = False
|
|
18
|
+
id: UUID
|
|
19
|
+
dl_file = None
|
|
20
|
+
firstframe = True
|
|
21
|
+
writer = None
|
|
22
|
+
rserv = None
|
|
23
|
+
rtp_params:RTPParams
|
|
24
|
+
state_lock: Lock
|
|
25
|
+
handlers: RTPHandlers
|
|
26
|
+
_rtp_server: Optional[RtpServer]
|
|
27
|
+
def __init__(self, rc:RTPConf, rtp_params:RTPParams, audio_in:callable,
|
|
28
|
+
handlers:RTPHandlers=None):
|
|
29
|
+
self.id = uuid4()
|
|
30
|
+
self.rtp_params = rtp_params
|
|
31
|
+
self.handlers = handlers or RTPHandlers()
|
|
32
|
+
self._palloc = rc.palloc
|
|
33
|
+
self._rtp_server = None
|
|
34
|
+
self.state_lock = Lock()
|
|
35
|
+
self.rsess = self.make_rtp_instream(rtp_params, audio_in)
|
|
36
|
+
rserv_opts = self.make_udp_server_opts(rtp_params)
|
|
37
|
+
self.rserv = self.make_udp_server(rserv_opts)
|
|
38
|
+
if self.rtp_params.rtp_target is not None:
|
|
39
|
+
self.writer_setup()
|
|
40
|
+
|
|
41
|
+
def make_writer(self, rtp_params:RTPParams):
|
|
42
|
+
return self.handlers.writer_cls(rtp_params)
|
|
43
|
+
|
|
44
|
+
def make_rtp_instream(self, rtp_params:RTPParams, audio_in:callable):
|
|
45
|
+
return self.handlers.rtp_instream_cls(rtp_params, audio_in)
|
|
46
|
+
|
|
47
|
+
def make_udp_server_opts(self, rtp_params:RTPParams):
|
|
48
|
+
palloc = self._palloc if rtp_params.rtp_lport == 0 else rtp_params.rtp_lport
|
|
49
|
+
return (rtp_params.rtp_laddr, palloc)
|
|
50
|
+
|
|
51
|
+
def _make_rtp_received_cb(self):
|
|
52
|
+
self_ref = weakref.ref(self)
|
|
53
|
+
|
|
54
|
+
def rtp_received_cb(data, address, rtime_ns=None):
|
|
55
|
+
self = self_ref()
|
|
56
|
+
if self is None:
|
|
57
|
+
return
|
|
58
|
+
return self.rtp_received(data, address, rtime_ns)
|
|
59
|
+
|
|
60
|
+
return rtp_received_cb
|
|
61
|
+
|
|
62
|
+
def _make_send_pkt_cb(self):
|
|
63
|
+
self_ref = weakref.ref(self)
|
|
64
|
+
|
|
65
|
+
def send_pkt_cb(pkt):
|
|
66
|
+
self = self_ref()
|
|
67
|
+
if self is None:
|
|
68
|
+
return
|
|
69
|
+
return self.send_pkt(pkt)
|
|
70
|
+
|
|
71
|
+
return send_pkt_cb
|
|
72
|
+
|
|
73
|
+
def make_udp_server(self, rserv_opts):
|
|
74
|
+
rtp_laddr, palloc = rserv_opts
|
|
75
|
+
server_cls = self.handlers.rtp_server_cls
|
|
76
|
+
rtp_server = acquire_rtp_server(server_cls)
|
|
77
|
+
channel = None
|
|
78
|
+
self._rtp_server = rtp_server
|
|
79
|
+
try:
|
|
80
|
+
channel = self._bind_channel(rtp_server, rtp_laddr, palloc)
|
|
81
|
+
target = self.rtp_params.rtp_target
|
|
82
|
+
if target is not None:
|
|
83
|
+
channel.set_target(target[0], target[1])
|
|
84
|
+
return channel
|
|
85
|
+
except Exception:
|
|
86
|
+
release_rtp_server(rtp_server)
|
|
87
|
+
self._rtp_server = None
|
|
88
|
+
if channel is not None:
|
|
89
|
+
channel.close()
|
|
90
|
+
raise
|
|
91
|
+
|
|
92
|
+
def _create_channel(self, rtp_server:RtpServer, bind_host:str, bind_port:int):
|
|
93
|
+
bind_family = self.rtp_params.rtp_family
|
|
94
|
+
ch_kwargs = dict(pkt_in=self._make_rtp_received_cb(), bind_host=bind_host, bind_port=bind_port)
|
|
95
|
+
ch = rtp_server.create_channel(bind_family=bind_family, **ch_kwargs)
|
|
96
|
+
self.rtp_params.rtp_lport = bind_port
|
|
97
|
+
return ch
|
|
98
|
+
|
|
99
|
+
def _bind_channel(self, rtp_server:RtpServer, rtp_laddr:str, palloc, preferred_port:int=None):
|
|
100
|
+
if preferred_port is not None:
|
|
101
|
+
try:
|
|
102
|
+
return self._create_channel(rtp_server, rtp_laddr, preferred_port)
|
|
103
|
+
except OSError as ex:
|
|
104
|
+
if ex.errno != errno.EADDRINUSE:
|
|
105
|
+
raise
|
|
106
|
+
if callable(palloc):
|
|
107
|
+
ntry = -1
|
|
108
|
+
while True:
|
|
109
|
+
ntry += 1
|
|
110
|
+
bind_port = int(palloc(ntry))
|
|
111
|
+
try:
|
|
112
|
+
return self._create_channel(rtp_server, rtp_laddr, bind_port)
|
|
113
|
+
except OSError as ex:
|
|
114
|
+
if ex.errno == errno.EADDRINUSE:
|
|
115
|
+
continue
|
|
116
|
+
raise
|
|
117
|
+
return self._create_channel(rtp_server, rtp_laddr, int(palloc))
|
|
118
|
+
|
|
119
|
+
def _swap_channel(self, old_channel, rtp_params:RTPParams):
|
|
120
|
+
with self.state_lock:
|
|
121
|
+
rtp_server = self._rtp_server
|
|
122
|
+
if rtp_server is None or self.rserv is not old_channel:
|
|
123
|
+
return
|
|
124
|
+
rtp_laddr, palloc = self.make_udp_server_opts(rtp_params)
|
|
125
|
+
preferred_port = old_channel.local_addr[1]
|
|
126
|
+
new_channel = self._bind_channel(rtp_server, rtp_laddr, palloc, preferred_port=preferred_port)
|
|
127
|
+
target = rtp_params.rtp_target
|
|
128
|
+
if target is not None:
|
|
129
|
+
new_channel.set_target(target[0], target[1])
|
|
130
|
+
with self.state_lock:
|
|
131
|
+
if self._rtp_server is None or self.rserv is not old_channel:
|
|
132
|
+
new_channel.close()
|
|
133
|
+
return
|
|
134
|
+
self.rserv = new_channel
|
|
135
|
+
old_channel.close()
|
|
136
|
+
|
|
137
|
+
def writer_setup(self):
|
|
138
|
+
assert self.writer is None
|
|
139
|
+
writer = self.make_writer(self.rtp_params)
|
|
140
|
+
writer.set_pkt_send_f(self._make_send_pkt_cb())
|
|
141
|
+
if self.dl_file is not None:
|
|
142
|
+
writer.enable_datalog(self.dl_file)
|
|
143
|
+
writer.start()
|
|
144
|
+
self.writer = writer
|
|
145
|
+
|
|
146
|
+
def send_pkt(self, pkt):
|
|
147
|
+
with self.state_lock:
|
|
148
|
+
channel = self.rserv
|
|
149
|
+
assert channel is not None
|
|
150
|
+
try:
|
|
151
|
+
channel.send_pkt(pkt)
|
|
152
|
+
except RtpQueueFullError:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
def rtp_received(self, data, address, rtime_ns=None):
|
|
156
|
+
#self.dprint(f"RTP.Ingest.rtp_received: len(data) = {len(data)}")
|
|
157
|
+
if rtime_ns is None:
|
|
158
|
+
rtime = MonoTime().monot
|
|
159
|
+
else:
|
|
160
|
+
rtime = rtime_ns / 1_000_000_000.0
|
|
161
|
+
with self.state_lock:
|
|
162
|
+
target = self.rtp_params.rtp_target
|
|
163
|
+
if target is not None and address != target:
|
|
164
|
+
if self.debug:
|
|
165
|
+
print(f"InfernRTPIngest.rtp_received: address mismatch {address=} {self.rtp_params.rtp_target=}")
|
|
166
|
+
return
|
|
167
|
+
self.rsess.rtp_received(data, address, rtime)
|
|
168
|
+
|
|
169
|
+
def update(self, rtp_params:RTPParams):
|
|
170
|
+
old_writer = None
|
|
171
|
+
need_new_writer = False
|
|
172
|
+
target_changed = False
|
|
173
|
+
proto_changed = False
|
|
174
|
+
with self.state_lock:
|
|
175
|
+
target_changed = self.rtp_params.rtp_target != rtp_params.rtp_target
|
|
176
|
+
proto_changed = self.rtp_params.rtp_proto != rtp_params.rtp_proto
|
|
177
|
+
self.rtp_params.rtp_proto = rtp_params.rtp_proto
|
|
178
|
+
self.rtp_params.rtp_target = rtp_params.rtp_target
|
|
179
|
+
ptime_changed = self.rtp_params.out_ptime != rtp_params.out_ptime
|
|
180
|
+
self.rtp_params.out_ptime = rtp_params.out_ptime
|
|
181
|
+
self.rtp_params.out_sr = rtp_params.out_sr
|
|
182
|
+
self.rtp_params.codec = rtp_params.codec
|
|
183
|
+
channel = self.rserv
|
|
184
|
+
if self.rtp_params.rtp_target is None:
|
|
185
|
+
old_writer = self.writer
|
|
186
|
+
self.writer = None
|
|
187
|
+
elif self.writer is None:
|
|
188
|
+
need_new_writer = True
|
|
189
|
+
elif ptime_changed or proto_changed:
|
|
190
|
+
old_writer = self.writer
|
|
191
|
+
self.writer = None
|
|
192
|
+
need_new_writer = True
|
|
193
|
+
if old_writer is not None:
|
|
194
|
+
old_writer.end()
|
|
195
|
+
old_writer.join()
|
|
196
|
+
if proto_changed and channel is not None:
|
|
197
|
+
self._swap_channel(channel, rtp_params)
|
|
198
|
+
elif target_changed and channel is not None and self.rtp_params.rtp_target is not None:
|
|
199
|
+
target = self.rtp_params.rtp_target
|
|
200
|
+
channel.set_target(target[0], target[1])
|
|
201
|
+
if need_new_writer:
|
|
202
|
+
new_writer = self.make_writer(rtp_params)
|
|
203
|
+
new_writer.set_pkt_send_f(self._make_send_pkt_cb())
|
|
204
|
+
if self.dl_file is not None:
|
|
205
|
+
new_writer.enable_datalog(self.dl_file)
|
|
206
|
+
new_writer.start()
|
|
207
|
+
with self.state_lock:
|
|
208
|
+
if self.rserv is None or self.rtp_params.rtp_target is None:
|
|
209
|
+
# RTP endpoint has been shut down while swapping writer.
|
|
210
|
+
new_writer.end()
|
|
211
|
+
new_writer.join()
|
|
212
|
+
else:
|
|
213
|
+
self.writer = new_writer
|
|
214
|
+
self.rsess.stream_update()
|
|
215
|
+
|
|
216
|
+
def connect(self, ain:callable):
|
|
217
|
+
self.rsess.stream_connect(ain)
|
|
218
|
+
|
|
219
|
+
def shutdown(self):
|
|
220
|
+
with self.state_lock:
|
|
221
|
+
writer, channel, rtp_server = self.writer, self.rserv, self._rtp_server
|
|
222
|
+
self.writer, self.rserv, self._rtp_server = (None, None, None)
|
|
223
|
+
if writer is not None:
|
|
224
|
+
writer.end()
|
|
225
|
+
writer.join()
|
|
226
|
+
if channel is not None:
|
|
227
|
+
channel.close()
|
|
228
|
+
if rtp_server is not None:
|
|
229
|
+
release_rtp_server(rtp_server)
|
|
230
|
+
|
|
231
|
+
def __del__(self):
|
|
232
|
+
if self.debug:
|
|
233
|
+
print('RTP.EPoint.__del__')
|
|
234
|
+
self.shutdown()
|
|
235
|
+
|
|
236
|
+
def soundout(self, chunk:AudioChunk):
|
|
237
|
+
if self.firstframe:
|
|
238
|
+
if self.debug:
|
|
239
|
+
nframes = chunk.nframes if hasattr(chunk, 'nframes') else len(chunk.audio)
|
|
240
|
+
print(f'RTP.EPoint.soundout[{str(self.id)[:6]}]: {nframes}')
|
|
241
|
+
self.firstframe = False
|
|
242
|
+
with self.state_lock:
|
|
243
|
+
if self.writer is None: return
|
|
244
|
+
return self.writer.soundout(chunk)
|