python-aidot-cameras 0.10.2__tar.gz → 0.10.3__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 (121) hide show
  1. {python_aidot_cameras-0.10.2/python_aidot_cameras.egg-info → python_aidot_cameras-0.10.3}/PKG-INFO +8 -6
  2. python_aidot_cameras-0.10.3/aidot/_vendor/__init__.py +2 -0
  3. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/__init__.py +100 -0
  4. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/clock.py +29 -0
  5. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/codecs/__init__.py +190 -0
  6. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/codecs/base.py +24 -0
  7. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/codecs/g711.py +89 -0
  8. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/codecs/g722.py +77 -0
  9. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/codecs/h264.py +319 -0
  10. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/codecs/opus.py +77 -0
  11. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/codecs/vpx.py +286 -0
  12. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/contrib/media.py +637 -0
  13. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/contrib/signaling.py +251 -0
  14. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/events.py +20 -0
  15. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/exceptions.py +14 -0
  16. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/jitterbuffer.py +124 -0
  17. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/mediastreams.py +148 -0
  18. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rate.py +579 -0
  19. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rtcconfiguration.py +66 -0
  20. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rtcdatachannel.py +221 -0
  21. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rtcdtlstransport.py +754 -0
  22. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rtcicetransport.py +377 -0
  23. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rtcpeerconnection.py +1397 -0
  24. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rtcrtpparameters.py +170 -0
  25. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rtcrtpreceiver.py +624 -0
  26. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rtcrtpsender.py +487 -0
  27. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rtcrtptransceiver.py +154 -0
  28. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rtcsctptransport.py +1832 -0
  29. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rtcsessiondescription.py +19 -0
  30. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/rtp.py +790 -0
  31. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/sdp.py +588 -0
  32. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/stats.py +114 -0
  33. python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/utils.py +54 -0
  34. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/protocol.py +1 -1
  35. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/webrtc_open.py +7 -7
  36. python_aidot_cameras-0.10.3/aidot/py.typed +0 -0
  37. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/pyproject.toml +26 -12
  38. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3/python_aidot_cameras.egg-info}/PKG-INFO +8 -6
  39. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/python_aidot_cameras.egg-info/SOURCES.txt +33 -0
  40. python_aidot_cameras-0.10.3/python_aidot_cameras.egg-info/requires.txt +16 -0
  41. python_aidot_cameras-0.10.2/python_aidot_cameras.egg-info/requires.txt +0 -14
  42. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/LICENSE +0 -0
  43. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/MANIFEST.in +0 -0
  44. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/README.md +0 -0
  45. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/__init__.py +0 -0
  46. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/__main__.py +0 -0
  47. /python_aidot_cameras-0.10.2/aidot/py.typed → /python_aidot_cameras-0.10.3/aidot/_vendor/aiortc/contrib/__init__.py +0 -0
  48. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/aes_utils.py +0 -0
  49. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/__init__.py +0 -0
  50. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/client.py +0 -0
  51. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/constants.py +0 -0
  52. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/controls.py +0 -0
  53. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/go2rtc.py +0 -0
  54. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/lan_control.py +0 -0
  55. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/models.py +0 -0
  56. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/playback.py +0 -0
  57. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/sdes.py +0 -0
  58. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/sdes_open.py +0 -0
  59. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/tutk.py +0 -0
  60. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/camera/webrtc.py +0 -0
  61. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/client.py +0 -0
  62. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/const.py +0 -0
  63. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/credentials.py +0 -0
  64. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/device_client.py +0 -0
  65. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/discover.py +0 -0
  66. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/exceptions.py +0 -0
  67. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/g711.py +0 -0
  68. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/login_const.py +0 -0
  69. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/models/__init__.py +0 -0
  70. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/models/device_client_model.py +0 -0
  71. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/models/device_model.py +0 -0
  72. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/aidot/models/discover_model.py +0 -0
  73. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  74. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
  75. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/python_aidot_cameras.egg-info/top_level.txt +0 -0
  76. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/setup.cfg +0 -0
  77. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_aioice_compat.py +0 -0
  78. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_alarm_event.py +0 -0
  79. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_backoff.py +0 -0
  80. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_device_login_guard.py +0 -0
  81. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_device_user_info_cache.py +0 -0
  82. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_dtls_not_ready_burst.py +0 -0
  83. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_dtls_pinning.py +0 -0
  84. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_dtls_skip_signaling_wait.py +0 -0
  85. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_egress_guard.py +0 -0
  86. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_go2rtc.py +0 -0
  87. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_go2rtc_cli.py +0 -0
  88. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_highport_nomination.py +0 -0
  89. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_ice_config_cache.py +0 -0
  90. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_keyframe_prompter.py +0 -0
  91. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_lan_control.py +0 -0
  92. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_live_stream_param.py +0 -0
  93. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_motion_poll.py +0 -0
  94. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_narrow_pc_ice.py +0 -0
  95. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_no_undefined_names.py +0 -0
  96. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_open_gate_delay.py +0 -0
  97. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_persistent_mqtt.py +0 -0
  98. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_persistent_mqtt_robustness.py +0 -0
  99. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_playback_tls.py +0 -0
  100. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_post_merge_hardening.py +0 -0
  101. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_retry_policy.py +0 -0
  102. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_sdes_adaptive.py +0 -0
  103. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_sdes_echo_wait_timeout.py +0 -0
  104. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_sdes_fast_liveplay.py +0 -0
  105. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_sdes_idle_release.py +0 -0
  106. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_sdes_serve_audio.py +0 -0
  107. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_sdes_serve_cmd.py +0 -0
  108. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_sdes_sprop.py +0 -0
  109. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_sdes_talk.py +0 -0
  110. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_sdes_watchdog.py +0 -0
  111. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_security_hardening.py +0 -0
  112. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_serve_relay.py +0 -0
  113. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_session_stats.py +0 -0
  114. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_speak.py +0 -0
  115. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_stream_cap.py +0 -0
  116. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_stream_idle.py +0 -0
  117. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_stream_teardown.py +0 -0
  118. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_talk.py +0 -0
  119. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_terminal_ack.py +0 -0
  120. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_token_refresh.py +0 -0
  121. {python_aidot_cameras-0.10.2 → python_aidot_cameras-0.10.3}/tests/test_wait_or_event.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.10.2
3
+ Version: 0.10.3
4
4
  Summary: Control AiDot/Leedarson WiFi lights and cameras (WebRTC streaming, two-way audio, PTZ, controls)
5
5
  Author-email: cbrightly <chris.brightly@gmail.com>
6
6
  License-Expression: MIT
@@ -17,11 +17,13 @@ Requires-Dist: cryptography>=42.0
17
17
  Requires-Dist: pycryptodome>=3.20
18
18
  Requires-Dist: dacite>=1.8
19
19
  Provides-Extra: webrtc
20
- Requires-Dist: aiortc>=1.9.0; extra == "webrtc"
21
- Requires-Dist: aioice<0.12,>=0.9.0; extra == "webrtc"
22
- Requires-Dist: av; extra == "webrtc"
23
- Requires-Dist: pylibsrtp; extra == "webrtc"
24
- Requires-Dist: pyopenssl; extra == "webrtc"
20
+ Requires-Dist: av<18.0.0,>=14.0.0; extra == "webrtc"
21
+ Requires-Dist: aioice<0.12,>=0.10.1; extra == "webrtc"
22
+ Requires-Dist: pylibsrtp>=0.10.0; extra == "webrtc"
23
+ Requires-Dist: pyopenssl>=25.0.0; extra == "webrtc"
24
+ Requires-Dist: cryptography>=44.0.0; extra == "webrtc"
25
+ Requires-Dist: pyee>=13.0.0; extra == "webrtc"
26
+ Requires-Dist: google-crc32c>=1.1; extra == "webrtc"
25
27
  Requires-Dist: numpy; extra == "webrtc"
26
28
  Requires-Dist: Pillow; extra == "webrtc"
27
29
  Dynamic: license-file
@@ -0,0 +1,2 @@
1
+ """Vendored third-party packages. See aiortc/ (byte-identical to aiortc
2
+ 1.14.0; vendored so HA 2026.7 av==17 is satisfiable, stock aiortc caps av<17)."""
@@ -0,0 +1,100 @@
1
+ # ruff: noqa: F401
2
+ import logging
3
+
4
+ from .exceptions import InvalidAccessError, InvalidStateError
5
+ from .mediastreams import (
6
+ AudioStreamTrack,
7
+ MediaStreamError,
8
+ MediaStreamTrack,
9
+ VideoStreamTrack,
10
+ )
11
+ from .rtcconfiguration import RTCBundlePolicy, RTCConfiguration, RTCIceServer
12
+ from .rtcdatachannel import RTCDataChannel, RTCDataChannelParameters
13
+ from .rtcdtlstransport import (
14
+ RTCCertificate,
15
+ RTCDtlsFingerprint,
16
+ RTCDtlsParameters,
17
+ RTCDtlsTransport,
18
+ )
19
+ from .rtcicetransport import (
20
+ RTCIceCandidate,
21
+ RTCIceGatherer,
22
+ RTCIceParameters,
23
+ RTCIceTransport,
24
+ )
25
+ from .rtcpeerconnection import RTCPeerConnection
26
+ from .rtcrtpparameters import (
27
+ RTCRtcpParameters,
28
+ RTCRtpCapabilities,
29
+ RTCRtpCodecCapability,
30
+ RTCRtpCodecParameters,
31
+ RTCRtpHeaderExtensionCapability,
32
+ RTCRtpHeaderExtensionParameters,
33
+ RTCRtpParameters,
34
+ )
35
+ from .rtcrtpreceiver import (
36
+ RTCRtpContributingSource,
37
+ RTCRtpReceiver,
38
+ RTCRtpSynchronizationSource,
39
+ )
40
+ from .rtcrtpsender import RTCRtpSender
41
+ from .rtcrtptransceiver import RTCRtpTransceiver
42
+ from .rtcsctptransport import RTCSctpCapabilities, RTCSctpTransport
43
+ from .rtcsessiondescription import RTCSessionDescription
44
+ from .stats import (
45
+ RTCInboundRtpStreamStats,
46
+ RTCOutboundRtpStreamStats,
47
+ RTCRemoteInboundRtpStreamStats,
48
+ RTCRemoteOutboundRtpStreamStats,
49
+ RTCStatsReport,
50
+ RTCTransportStats,
51
+ )
52
+
53
+ __version__ = "1.14.0"
54
+
55
+ # Set default logging handler to avoid "No handler found" warnings.
56
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
57
+
58
+ __all__ = [
59
+ "AudioStreamTrack",
60
+ "InvalidAccessError",
61
+ "InvalidStateError",
62
+ "MediaStreamError",
63
+ "MediaStreamTrack",
64
+ "RTCBundlePolicy",
65
+ "RTCCertificate",
66
+ "RTCConfiguration",
67
+ "RTCDataChannel",
68
+ "RTCDataChannelParameters",
69
+ "RTCDtlsFingerprint",
70
+ "RTCDtlsParameters",
71
+ "RTCDtlsTransport",
72
+ "RTCIceCandidate",
73
+ "RTCIceGatherer",
74
+ "RTCIceParameters",
75
+ "RTCIceServer",
76
+ "RTCIceTransport",
77
+ "RTCInboundRtpStreamStats",
78
+ "RTCOutboundRtpStreamStats",
79
+ "RTCPeerConnection",
80
+ "RTCRemoteInboundRtpStreamStats",
81
+ "RTCRemoteOutboundRtpStreamStats",
82
+ "RTCRtcpParameters",
83
+ "RTCRtpCapabilities",
84
+ "RTCRtpCodecCapability",
85
+ "RTCRtpCodecParameters",
86
+ "RTCRtpContributingSource",
87
+ "RTCRtpHeaderExtensionCapability",
88
+ "RTCRtpHeaderExtensionParameters",
89
+ "RTCRtpParameters",
90
+ "RTCRtpReceiver",
91
+ "RTCRtpSender",
92
+ "RTCRtpSynchronizationSource",
93
+ "RTCRtpTransceiver",
94
+ "RTCSctpCapabilities",
95
+ "RTCSctpTransport",
96
+ "RTCSessionDescription",
97
+ "RTCStatsReport",
98
+ "RTCTransportStats",
99
+ "VideoStreamTrack",
100
+ ]
@@ -0,0 +1,29 @@
1
+ import datetime
2
+
3
+ NTP_EPOCH = datetime.datetime(1900, 1, 1, tzinfo=datetime.timezone.utc)
4
+
5
+
6
+ def current_datetime() -> datetime.datetime:
7
+ return datetime.datetime.now(datetime.timezone.utc)
8
+
9
+
10
+ def current_ms() -> int:
11
+ delta = current_datetime() - NTP_EPOCH
12
+ return int(delta.total_seconds() * 1000)
13
+
14
+
15
+ def current_ntp_time() -> int:
16
+ return datetime_to_ntp(current_datetime())
17
+
18
+
19
+ def datetime_from_ntp(ntp: int) -> datetime.datetime:
20
+ seconds = ntp >> 32
21
+ microseconds = ((ntp & 0xFFFFFFFF) * 1000000) / (1 << 32)
22
+ return NTP_EPOCH + datetime.timedelta(seconds=seconds, microseconds=microseconds)
23
+
24
+
25
+ def datetime_to_ntp(dt: datetime.datetime) -> int:
26
+ delta = dt - NTP_EPOCH
27
+ high = int(delta.total_seconds())
28
+ low = round((delta.microseconds * (1 << 32)) // 1000000)
29
+ return (high << 32) | low
@@ -0,0 +1,190 @@
1
+ from typing import Optional, Union
2
+
3
+ from ..rtcrtpparameters import (
4
+ ParametersDict,
5
+ RTCRtcpFeedback,
6
+ RTCRtpCapabilities,
7
+ RTCRtpCodecCapability,
8
+ RTCRtpCodecParameters,
9
+ RTCRtpHeaderExtensionCapability,
10
+ RTCRtpHeaderExtensionParameters,
11
+ )
12
+ from .base import Decoder, Encoder
13
+ from .g711 import PcmaDecoder, PcmaEncoder, PcmuDecoder, PcmuEncoder
14
+ from .g722 import G722Decoder, G722Encoder
15
+ from .h264 import H264Decoder, H264Encoder, h264_depayload
16
+ from .opus import OpusDecoder, OpusEncoder
17
+ from .vpx import Vp8Decoder, Vp8Encoder, vp8_depayload
18
+
19
+ # The clockrate for G.722 is 8kHz even though the sampling rate is 16kHz.
20
+ # See https://datatracker.ietf.org/doc/html/rfc3551
21
+ G722_CODEC = RTCRtpCodecParameters(
22
+ mimeType="audio/G722", clockRate=8000, channels=1, payloadType=9
23
+ )
24
+ PCMU_CODEC = RTCRtpCodecParameters(
25
+ mimeType="audio/PCMU", clockRate=8000, channels=1, payloadType=0
26
+ )
27
+ PCMA_CODEC = RTCRtpCodecParameters(
28
+ mimeType="audio/PCMA", clockRate=8000, channels=1, payloadType=8
29
+ )
30
+
31
+ CODECS: dict[str, list[RTCRtpCodecParameters]] = {
32
+ "audio": [
33
+ RTCRtpCodecParameters(
34
+ mimeType="audio/opus", clockRate=48000, channels=2, payloadType=96
35
+ ),
36
+ G722_CODEC,
37
+ PCMU_CODEC,
38
+ PCMA_CODEC,
39
+ ],
40
+ "video": [],
41
+ }
42
+ # Note, the id space for these extensions is shared across media types when BUNDLE
43
+ # is negotiated. If you add a audio- or video-specific extension, make sure it has
44
+ # a unique id.
45
+ HEADER_EXTENSIONS: dict[str, list[RTCRtpHeaderExtensionParameters]] = {
46
+ "audio": [
47
+ RTCRtpHeaderExtensionParameters(
48
+ id=1, uri="urn:ietf:params:rtp-hdrext:sdes:mid"
49
+ ),
50
+ RTCRtpHeaderExtensionParameters(
51
+ id=2, uri="urn:ietf:params:rtp-hdrext:ssrc-audio-level"
52
+ ),
53
+ ],
54
+ "video": [
55
+ RTCRtpHeaderExtensionParameters(
56
+ id=1, uri="urn:ietf:params:rtp-hdrext:sdes:mid"
57
+ ),
58
+ RTCRtpHeaderExtensionParameters(
59
+ id=3, uri="http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"
60
+ ),
61
+ ],
62
+ }
63
+
64
+
65
+ def init_codecs() -> None:
66
+ dynamic_pt = 97
67
+
68
+ def add_video_codec(
69
+ mimeType: str, parameters: Optional[ParametersDict] = None
70
+ ) -> None:
71
+ nonlocal dynamic_pt
72
+
73
+ clockRate = 90000
74
+ CODECS["video"] += [
75
+ RTCRtpCodecParameters(
76
+ mimeType=mimeType,
77
+ clockRate=clockRate,
78
+ payloadType=dynamic_pt,
79
+ rtcpFeedback=[
80
+ RTCRtcpFeedback(type="nack"),
81
+ RTCRtcpFeedback(type="nack", parameter="pli"),
82
+ RTCRtcpFeedback(type="goog-remb"),
83
+ ],
84
+ parameters=parameters or {},
85
+ ),
86
+ RTCRtpCodecParameters(
87
+ mimeType="video/rtx",
88
+ clockRate=clockRate,
89
+ payloadType=dynamic_pt + 1,
90
+ parameters={"apt": dynamic_pt},
91
+ ),
92
+ ]
93
+ dynamic_pt += 2
94
+
95
+ add_video_codec("video/VP8")
96
+ for profile_level_id in ("42001f", "42e01f"):
97
+ add_video_codec(
98
+ "video/H264",
99
+ {
100
+ "level-asymmetry-allowed": "1",
101
+ "packetization-mode": "1",
102
+ "profile-level-id": profile_level_id,
103
+ },
104
+ )
105
+
106
+
107
+ def depayload(codec: RTCRtpCodecParameters, payload: bytes) -> bytes:
108
+ if codec.name == "VP8":
109
+ return vp8_depayload(payload)
110
+ elif codec.name == "H264":
111
+ return h264_depayload(payload)
112
+ else:
113
+ return payload
114
+
115
+
116
+ def get_capabilities(kind: str) -> RTCRtpCapabilities:
117
+ if kind not in CODECS:
118
+ raise ValueError(f"cannot get capabilities for unknown media {kind}")
119
+
120
+ codecs = []
121
+ rtx_added = False
122
+ for params in CODECS[kind]:
123
+ if not is_rtx(params):
124
+ codecs.append(
125
+ RTCRtpCodecCapability(
126
+ mimeType=params.mimeType,
127
+ clockRate=params.clockRate,
128
+ channels=params.channels,
129
+ parameters=params.parameters,
130
+ )
131
+ )
132
+ elif not rtx_added:
133
+ # There will only be a single entry in codecs[] for retransmission
134
+ # via RTX, with sdpFmtpLine not present.
135
+ codecs.append(
136
+ RTCRtpCodecCapability(
137
+ mimeType=params.mimeType, clockRate=params.clockRate
138
+ )
139
+ )
140
+ rtx_added = True
141
+
142
+ headerExtensions = []
143
+ for extension in HEADER_EXTENSIONS[kind]:
144
+ headerExtensions.append(RTCRtpHeaderExtensionCapability(uri=extension.uri))
145
+ return RTCRtpCapabilities(codecs=codecs, headerExtensions=headerExtensions)
146
+
147
+
148
+ def get_decoder(codec: RTCRtpCodecParameters) -> Decoder:
149
+ mimeType = codec.mimeType.lower()
150
+
151
+ if mimeType == "audio/g722":
152
+ return G722Decoder()
153
+ elif mimeType == "audio/opus":
154
+ return OpusDecoder()
155
+ elif mimeType == "audio/pcma":
156
+ return PcmaDecoder()
157
+ elif mimeType == "audio/pcmu":
158
+ return PcmuDecoder()
159
+ elif mimeType == "video/h264":
160
+ return H264Decoder()
161
+ elif mimeType == "video/vp8":
162
+ return Vp8Decoder()
163
+ else:
164
+ raise ValueError(f"No decoder found for MIME type `{mimeType}`")
165
+
166
+
167
+ def get_encoder(codec: RTCRtpCodecParameters) -> Encoder:
168
+ mimeType = codec.mimeType.lower()
169
+
170
+ if mimeType == "audio/g722":
171
+ return G722Encoder()
172
+ elif mimeType == "audio/opus":
173
+ return OpusEncoder()
174
+ elif mimeType == "audio/pcma":
175
+ return PcmaEncoder()
176
+ elif mimeType == "audio/pcmu":
177
+ return PcmuEncoder()
178
+ elif mimeType == "video/h264":
179
+ return H264Encoder()
180
+ elif mimeType == "video/vp8":
181
+ return Vp8Encoder()
182
+ else:
183
+ raise ValueError(f"No encoder found for MIME type `{mimeType}`")
184
+
185
+
186
+ def is_rtx(codec: Union[RTCRtpCodecCapability, RTCRtpCodecParameters]) -> bool:
187
+ return codec.name.lower() == "rtx"
188
+
189
+
190
+ init_codecs()
@@ -0,0 +1,24 @@
1
+ from abc import ABCMeta, abstractmethod
2
+
3
+ from av.frame import Frame
4
+ from av.packet import Packet
5
+
6
+ from ..jitterbuffer import JitterFrame
7
+
8
+
9
+ class Decoder(metaclass=ABCMeta):
10
+ @abstractmethod
11
+ def decode(self, encoded_frame: JitterFrame) -> list[Frame]:
12
+ pass # pragma: no cover
13
+
14
+
15
+ class Encoder(metaclass=ABCMeta):
16
+ @abstractmethod
17
+ def encode(
18
+ self, frame: Frame, force_keyframe: bool = False
19
+ ) -> tuple[list[bytes], int]:
20
+ pass # pragma: no cover
21
+
22
+ @abstractmethod
23
+ def pack(self, packet: Packet) -> tuple[list[bytes], int]:
24
+ pass # pragma: no cover
@@ -0,0 +1,89 @@
1
+ import fractions
2
+ from typing import Literal, cast
3
+
4
+ from av import AudioFrame, AudioResampler, CodecContext
5
+ from av.frame import Frame
6
+ from av.packet import Packet
7
+
8
+ from ..jitterbuffer import JitterFrame
9
+ from ..mediastreams import convert_timebase
10
+ from .base import Decoder, Encoder
11
+
12
+ SAMPLE_RATE = 8000
13
+ SAMPLE_WIDTH = 2
14
+ SAMPLES_PER_FRAME = 160
15
+ TIME_BASE = fractions.Fraction(1, 8000)
16
+
17
+
18
+ class PcmDecoder(Decoder):
19
+ def __init__(self, codec_name: Literal["pcm_alaw", "pcm_mulaw"]) -> None:
20
+ self.codec = CodecContext.create(codec_name, "r")
21
+ self.codec.format = "s16"
22
+ self.codec.layout = "mono"
23
+ self.codec.sample_rate = SAMPLE_RATE
24
+
25
+ def decode(self, encoded_frame: JitterFrame) -> list[Frame]:
26
+ packet = Packet(encoded_frame.data)
27
+ packet.pts = encoded_frame.timestamp
28
+ packet.time_base = TIME_BASE
29
+ return cast(list[Frame], self.codec.decode(packet))
30
+
31
+
32
+ class PcmEncoder(Encoder):
33
+ def __init__(self, codec_name: Literal["pcm_alaw", "pcm_mulaw"]) -> None:
34
+ self.codec = CodecContext.create(codec_name, "w")
35
+ self.codec.format = "s16"
36
+ self.codec.layout = "mono"
37
+ self.codec.sample_rate = SAMPLE_RATE
38
+ self.codec.time_base = TIME_BASE
39
+
40
+ # Create our own resampler to control the frame size.
41
+ self.resampler = AudioResampler(
42
+ format="s16",
43
+ layout="mono",
44
+ rate=SAMPLE_RATE,
45
+ frame_size=SAMPLES_PER_FRAME,
46
+ )
47
+
48
+ def encode(
49
+ self, frame: Frame, force_keyframe: bool = False
50
+ ) -> tuple[list[bytes], int]:
51
+ assert isinstance(frame, AudioFrame)
52
+ assert frame.format.name == "s16"
53
+ assert frame.layout.name in ["mono", "stereo"]
54
+
55
+ # Send frame through resampler and encoder.
56
+ packets = []
57
+ for frame in self.resampler.resample(frame):
58
+ packets += self.codec.encode(frame)
59
+
60
+ if packets:
61
+ # Packets were returned.
62
+ return [bytes(p) for p in packets], packets[0].pts
63
+ else:
64
+ # No packets were returned due to buffering.
65
+ return [], None
66
+
67
+ def pack(self, packet: Packet) -> tuple[list[bytes], int]:
68
+ timestamp = convert_timebase(packet.pts, packet.time_base, TIME_BASE)
69
+ return [bytes(packet)], timestamp
70
+
71
+
72
+ class PcmaDecoder(PcmDecoder):
73
+ def __init__(self) -> None:
74
+ super().__init__("pcm_alaw")
75
+
76
+
77
+ class PcmaEncoder(PcmEncoder):
78
+ def __init__(self) -> None:
79
+ super().__init__("pcm_alaw")
80
+
81
+
82
+ class PcmuDecoder(PcmDecoder):
83
+ def __init__(self) -> None:
84
+ super().__init__("pcm_mulaw")
85
+
86
+
87
+ class PcmuEncoder(PcmEncoder):
88
+ def __init__(self) -> None:
89
+ super().__init__("pcm_mulaw")
@@ -0,0 +1,77 @@
1
+ import fractions
2
+ from typing import Optional, cast
3
+
4
+ from av import AudioCodecContext, AudioFrame, AudioResampler, CodecContext
5
+ from av.frame import Frame
6
+ from av.packet import Packet
7
+
8
+ from ..jitterbuffer import JitterFrame
9
+ from ..mediastreams import convert_timebase
10
+ from .base import Decoder, Encoder
11
+
12
+ SAMPLE_RATE = 16000
13
+ SAMPLE_WIDTH = 2
14
+ SAMPLES_PER_FRAME = 320
15
+ TIME_BASE = fractions.Fraction(1, 16000)
16
+
17
+ # Even though the sample rate is 16kHz, the clockrate is defined as 8kHz.
18
+ # This is why we have multiplications and divisions by 2 in the code.
19
+ CLOCK_BASE = fractions.Fraction(1, 8000)
20
+
21
+
22
+ class G722Decoder(Decoder):
23
+ def __init__(self) -> None:
24
+ self.codec = cast(AudioCodecContext, CodecContext.create("g722", "r"))
25
+ self.codec.format = "s16"
26
+ self.codec.layout = "mono"
27
+ self.codec.sample_rate = SAMPLE_RATE
28
+
29
+ def decode(self, encoded_frame: JitterFrame) -> list[Frame]:
30
+ packet = Packet(encoded_frame.data)
31
+ packet.pts = encoded_frame.timestamp * 2
32
+ packet.time_base = TIME_BASE
33
+ return cast(list[Frame], self.codec.decode(packet))
34
+
35
+
36
+ class G722Encoder(Encoder):
37
+ def __init__(self) -> None:
38
+ self.codec = cast(AudioCodecContext, CodecContext.create("g722", "w"))
39
+ self.codec.format = "s16"
40
+ self.codec.layout = "mono"
41
+ self.codec.sample_rate = SAMPLE_RATE
42
+ self.codec.time_base = TIME_BASE
43
+ self.first_pts: Optional[int] = None
44
+
45
+ # Create our own resampler to control the frame size.
46
+ self.resampler = AudioResampler(
47
+ format="s16",
48
+ layout="mono",
49
+ rate=SAMPLE_RATE,
50
+ frame_size=SAMPLES_PER_FRAME,
51
+ )
52
+
53
+ def encode(
54
+ self, frame: Frame, force_keyframe: bool = False
55
+ ) -> tuple[list[bytes], int]:
56
+ assert isinstance(frame, AudioFrame)
57
+ assert frame.format.name == "s16"
58
+ assert frame.layout.name in ["mono", "stereo"]
59
+
60
+ # Send frame through resampler and encoder.
61
+ packets = []
62
+ for frame in self.resampler.resample(frame):
63
+ packets += self.codec.encode(frame)
64
+
65
+ if packets:
66
+ # Packets were returned.
67
+ if self.first_pts is None:
68
+ self.first_pts = packets[0].pts
69
+ timestamp = (packets[0].pts - self.first_pts) // 2
70
+ return [bytes(p) for p in packets], timestamp
71
+ else:
72
+ # No packets were returned due to buffering.
73
+ return [], None
74
+
75
+ def pack(self, packet: Packet) -> tuple[list[bytes], int]:
76
+ timestamp = convert_timebase(packet.pts, packet.time_base, CLOCK_BASE)
77
+ return [bytes(packet)], timestamp