Welcome to pytgvoip¶
Telegram VoIP Library for Python
PytgVoIP is a Telegram VoIP library written in Python and C++.
It uses libtgvoip (a library used in official clients) for voice encoding and transmission, and pybind11 for simple generation of Python extension written in C++.
Features¶
- Making and receiving Telegram calls
- Python callbacks for sending and receiving audio stream frames allow flexible control
- Pre-built Windows wheels in PyPI
Requirements¶
- Python 3.4 or higher
- An MTProto client (i.e. Pyrogram, Telethon)
Installing¶
Refer the corresponding section: Installation
Encoding audio streams¶
Streams consumed by libtgvoip
should be encoded in 16-bit signed PCM
audio.
$ ffmpeg -i input.mp3 -f s16le -ac 1 -ar 48000 -acodec pcm_s16le input.raw # encode
$ ffmpeg -f s16le -ac 1 -ar 48000 -acodec pcm_s16le -i output.raw output.mp3 # decode
Copyright & License¶
- Copyright (C) 2019 bakatrouble
- Licensed under the terms of the GNU Lesser General Public License v3 or later (LGPLv3+)
Installation¶
Requirements¶
On Linux and macOS to install this library you must have make
, cmake
, C++11 compatible compiler, Python headers, Opus and OpenSSL libraries and headers installed:
Debian-based distributions
$ apt install make cmake gcc g++ python3-dev gcc g++ openssl libssl-dev libopus0 libopus-dev
Archlinux-based distributions
$ pacman -S make cmake gcc python3 openssl opus
macOS
$ brew install make cmake gcc g++ python3 openssl opus
Install pytgvoip¶
Stable version:
$ pip install pytgvoip
Development version:
$ pip install git+https://github.com/bakatrouble/pytgvoip#egg=pytgvoip
Usage¶
Common¶
- You should have a protocol object:
phoneCallProtocol(min_layer=65, max_layer=VoIPController.CONNECTION_MAX_LAYER, udp_p2p=True, udp_reflector=True)
- All VoIP-related updates have type of
updatePhoneCall
withphone_call
field of typesphoneCallEmpty
,phoneCallWaiting
,phoneCallRequested
,phoneCallAccepted
,phoneCall
orphoneCallDiscarded
- Use
tgvoip.utils.generate_visualization()
withauth_key
andg_a
for outgoing org_a_or_b
for incoming calls to get emojis if you need them
Starting conversation¶
- Create a
VoIPController
instance - Call
tgvoip.VoIPController.set_send_audio_frame_callback()
(see docs for arguments) if needed, otherwise silence will be sent - Call
tgvoip.VoIPController.set_recv_audio_frame_callback()
(see docs for arguments) if needed, otherwise nothing will be done to incoming audio stream - Add state change handlers to
tgvoip.VoIPController.call_state_changed_handlers
(see docs for handler format) list if needed - Add signal bars change handlers to
tgvoip.VoIPController.signal_bars_changed_handlers
(see docs for handler format) list if needed - Invoke
help.getConfig()
(result is later referred asconfig
) - Call
tgvoip.VoIPController.set_config()
(arguments are:config.call_packet_timeout_ms / 1000., config.call_connect_timeout_ms / 1000., DataSaving.NEVER, call.id
) - Call
tgvoip.VoIPController.set_encryption_key()
(arguments are:i2b(auth_key), is_outgoing
whereis_outgoing
is a corresponding boolean value) - Build a
list
oftgvoip.Endpoint
objects fromcall.connection
(single) andcall.alternative_connections
(another list) - Call
tgvoip.VoIPController.set_remote_endpoints()
(arguments are:endpoints, call.p2p_allowed, False, call.protocol.max_layer
) - Call
tgvoip.VoIPController.start()
- Call
tgvoip.VoIPController.connect()
Discarding call¶
- Build
peer
:inputPhoneCall(id=call.id, access_hash=call.access_hash)
- Get call duration using
tgvoip.VoIPController.call_duration
- Get connection ID using
tgvoip.VoIPController.get_preferred_relay_id()
- Build a suitable
reason
object (types are:phoneCallDiscardReasonBusy
,phoneCallDiscardReasonDisconnect
,phoneCallDiscardReasonHangup
,phoneCallDiscardReasonMissed
) - Invoke
phone.discardCall(peer, duration, connection_id, reason)
. You might getCALL_ALREADY_DECLINED
error, this is fine - Destroy the
tgvoip.VoIPController
object
Ending conversation¶
- Send call rating and debug log if call ended normally (not failed): TBD
- Destroy the
tgvoip.VoIPController
object, everything will be done automatically
Making outgoing calls¶
- Get a
user_id
object for user you want to call (of typeinputPeerUser
) - Request a Diffie-Hellman config using
messages.getDhConfig(version=0, random_length=256)
- Check received config using
tgvoip.utils.check_dhc()
. If check is not passed, do not make the call. You might want to cache received config because check is expensive - Choose a random value
a
,1 < a < dhc.p-1
- Calculate
g_a
:pow(dhc.g, a, dhc.p)
- Calculate
g_a_hash
:sha256(g_a)
- Choose a random value
random_id
,0 <= random_id <= 0x7fffffff-1
- Invoke
phone.requestCall(user_id, random_id, g_a_hash, protocol)
- Wait for an update with
phoneCallAccepted
object, it means that other party has accepted the call. You also might get aphoneCallDiscarded
object, it means that other party has declined the call - If you have got a
phoneCallDiscarded
object, stop thetgvoip.VoIPController
. Otherwise, continue - Check a
g_b
value from receivedphoneCallAccepted
(later referred ascall
) object usingtgvoip.utils.check_g()
. If check is not passed, stop the call - Calculate
auth_key
:pow(call.g_b, a, dhc.p)
- Calculate
key_fingerprint
usingtgvoip.utils.calc_fingerprint()
- Build
peer
:inputPhoneCall(id=call.id, access_hash=call.access_hash)
- Invoke
phone.confirmCall(key_fingerprint, peer, g_a, protocol)
- Start the conversation
Receiving calls¶
- You will receive an update containing
phoneCallRequested
object (later referred ascall
). You might discard it right away (use0
for duration and connection_id) - Request a Diffie-Hellman config using
messages.getDhConfig(version=0, random_length=256)
- Check received config using
tgvoip.utils.check_dhc()
. If check is not passed, do not make the call. You might want to cache received config because check is expensive - Choose a random value
b
,1 < b < dhc.p-1
- Calculate
g_b
:pow(dhc.g, b, dhc.p)
- Save
call.g_a_hash
- Build
peer
:inputPhoneCall(id=call.id, access_hash=call.access_hash)
- Invoke
phone.acceptCall(peer, g_b, protocol)
. You might getCALL_ALREADY_DISCARDED
orCALL_ALREADY_ACCEPTED
errors, then you should stop current conversation. Also, if response containsphoneCallDiscarded
object you should stop the call - Wait for an update with
phoneCall
object (later referred ascall
) - Check that
call.g_a_or_b
is not empty andsha256(call.g_a_or_b)
equals tog_a_hash
you saved before. If it doesn’t match, stop the call - Check a
call.g_a_or_b
value object usingtgvoip.utils.check_g()
(second argument isdhc.p
). If check is not passed, stop the call - Calculate
auth_key
:pow(call.g_a_or_b, b, dhc.p)
- Calculate
key_fingerprint
usingtgvoip.utils.calc_fingerprint()
- Check that
key_fingerprint
you have just calculated matchescall.key_fingerprint
. If it doesn’t match, stop the call - Start the conversation
libtgvoip wrapper¶
VoIPController¶
-
class
tgvoip.
VoIPController
(persistent_state_file: str = '', debug=False, logs_dir='logs')[source]¶ A wrapper around C++ wrapper for libtgvoip
VoIPController
Parameters: - persistent_state_file (
str
, optional) – ?, empty to not use - debug (
bool
, optional) – Modifies logging behavior - logs_dir (
str
, optional) – Logs directory
- Class attributes:
- LIBTGVOIP_VERSION
- Used
libtgvoip
version - CONNECTION_MAX_LAYER
- Maximum layer supported by used
libtgvoip
version
-
persistent_state_file
¶ Value set in the constructor
-
call_state_changed_handlers
¶ list
of call state change callbacks, callbacks receive aCallState
object as argument
-
signal_bars_changed_handlers
¶ list
of signal bars count change callbacks, callbacks receive anint
object as argument
-
call_duration
¶ Current call duration in seconds as
int
if call was started, otherwise 0
-
set_proxy
(address: str, port: int = 1080, username: str = '', password: str = '')[source]¶ Set SOCKS5 proxy config
Parameters: - address (
str
) – Proxy hostname or IP address - port (
int
, optional) – Proxy port - username (
int
, optional) – Proxy username - password (
int
, optional) – Proxy password
Raises: ValueError
ifaddress
is empty- address (
-
set_encryption_key
(key: bytes, is_outgoing: bool)[source]¶ Set call auth key
Parameters: - key (
bytes
) – Auth key, must be exactly 256 bytes - is_outgoing (
bool
) – Is call outgoing
Raises: ValueError
if provided auth key has wrong length- key (
-
set_remote_endpoints
(endpoints: List[<sphinx.ext.autodoc.importer._MockObject object at 0x7fe972ba3a58>], allow_p2p: bool, tcp: bool, connection_max_layer: int)[source]¶ Set remote endpoints received in call object from Telegram.
Usually it’s
[call.connection] + call.alternative_connections
.You must build
Endpoint
objects from MTProtophoneConnection
objects and pass them in list.Parameters: - endpoints (
list
ofEndpoint
) – List of endpoints - allow_p2p (
bool
) – Is p2p connection allowed, usually call.p2p_allowed value is used - tcp (
bool
) – Connect via TCP, not recommended - connection_max_layer (
int
) – Use a value provided byVoIPController.CONNECTION_MAX_LAYER
Raises: ValueError
if either no endpoints are provided or endpoints without IPv4 or with wrongpeer_tag
(must be eitherNone
or have length of 16 bytes) are detected- endpoints (
-
set_network_type
(_type: tgvoip.tgvoip.NetType)[source]¶ Set network type
Parameters: _type ( NetType
) – Network type to set
-
set_mic_mute
(mute: bool)[source]¶ Set “microphone” state. If muted, audio is not being sent
Parameters: mute ( bool
) – Whether to mute “microphone”
-
set_config
(recv_timeout: float, init_timeout: float, data_saving_mode: tgvoip.tgvoip.DataSaving, call_id: int, enable_aec: bool = True, enable_ns: bool = True, enable_agc: bool = True, log_file_path: str = None, status_dump_path: str = None, log_packet_stats: bool = None)[source]¶ Set call config
Parameters: - recv_timeout (
float
) – Packet receive timeout, usually value received fromhelp.getConfig()
is used - init_timeout (
float
) – Packet init timeout, usually value received fromhelp.getConfig()
is used - data_saving_mode (
DataSaving
) – Data saving mode - call_id (
int
) – Call ID - enable_aec (
bool
, optional) – Whether to enable automatic echo cancellation, defaults toTrue
- enable_ns (
bool
, optional) – Whether to enable noise suppression, defaults toTrue
- enable_agc (
bool
, optional) – Whether to enable automatic gain control, defaults toTrue
- log_file_path (
str
, optional) – Call log file path, calculated automatically if not provided - status_dump_path (
str
, optional) – Status dump path, calculated automatically if not provided anddebug
is enabled - log_packet_stats (
bool
, optional) – Whether to log packet stats, defaults todebug
value
- recv_timeout (
-
debug_ctl
(request: int, param: int)[source]¶ Debugging options
Parameters: - request (
int
) – Option (1
for max bitrate,2
for packet loss (in percents),3
for toggling p2p,4
for toggling echo cancelling) - param (
int
) – Numeric value for options 1 and 2,0
or1
for options 3 and 4
- request (
-
get_preferred_relay_id
() → int[source]¶ Get preferred relay ID (used in
discardCall
MTProto request)Returns: int
ID
-
get_last_error
() → tgvoip.tgvoip.CallError[source]¶ Get last error type
Returns: CallError
matching last occurred error type
-
get_stats
() → <sphinx.ext.autodoc.importer._MockObject object at 0x7fe972ba3a20>[source]¶ Get call stats
Returns: Stats
object
-
set_audio_output_gain_control_enabled
(enabled: bool)[source]¶ Toggle output gain control
Parameters: enabled ( bool
) – Whether to enable output gain control
-
set_echo_cancellation_strength
(strength: int)[source]¶ Set echo cancellation strength, does nothing currently but was in Java bindings (?)
Parameters: strength ( int
) – Strength value
-
get_peer_capabilities
() → int[source]¶ Get peer capabilities
Returns: int
with bit mask, looks like it is used only for experimental features (group, video calls)
-
native_io
¶ Get native I/O status (file I/O implemented in C++)
Returns: bool
status (enabled or not)
-
play
(path: str) → bool[source]¶ Add a file to play queue for native I/O
Parameters: path ( str
) – File pathReturns: bool
whether opening the file was successful. File is not added to queue on failure.
-
play_on_hold
(paths: List[str]) → None[source]¶ Replace the hold queue for native I/O
Parameters: paths ( list
ofstr
) – List of file paths
-
set_output_file
(path: str) → bool[source]¶ Set output file for native I/O
Parameters: path ( str
) – File pathReturns: bool
whether opening the file was successful. Output file is not replaced on failure.
-
update_state
(state: tgvoip.tgvoip.CallState)[source]¶ Manually update state (only triggers handlers)
Parameters: state ( CallState
) – State to set
-
set_send_audio_frame_callback
(func: callable)[source]¶ Set callback providing audio data to send
Should accept one argument (
int
length of requested audio frame) and returnbytes
object with audio data encoded in 16-bit signed PCMIf returned object has insufficient length, it will be automatically padded with zero bytes
Parameters: func ( callable
) – Callback function
- persistent_state_file (
VoIPServerConfig¶
-
class
tgvoip.
VoIPServerConfig
(*args, **kwargs)[source]¶ Global server config class. This class contains default config in its source
-
classmethod
set_config
(_json: Union[str, dict])[source]¶ Set global server config
Parameters: _json ( str
|dict
) – either JSON-encoded object ordict
containing config values. Might be received from MTProtophone.getCallConfig()
call, if not set default values are usedRaises: Prints an error to stderr
if JSON parsing (forstr
argument) or encoding (fordict
argument) has occurred
-
classmethod
set_bitrate_config
(init_bitrate: int = 16000, max_bitrate: int = 20000, min_bitrate: int = 8000, decrease_step: int = 1000, increase_step: int = 1000)[source]¶ Helper method for setting bitrate options
Parameters: - init_bitrate (
int
) – Initial bitrate value - max_bitrate (
int
) – Maximum bitrate value - min_bitrate (
int
) – Minimum bitrate value - decrease_step (
int
) – Bitrate decrease step - increase_step (
int
) – Bitrate increase step
Raises: Same as
set_config()
- init_bitrate (
-
classmethod
Enums¶
-
class
tgvoip.
NetType
[source]¶ An enumeration of network types
- Members:
- UNKNOWN = 0
- GPRS = 1
- EDGE = 2
- NET_3G = 3
- HSPA = 4
- LTE = 5
- WIFI = 6
- ETHERNET = 7
- OTHER_HIGH_SPEED = 8
- OTHER_LOW_SPEED = 9
- DIALUP = 10
- OTHER_MOBILE = 11
-
class
tgvoip.
DataSaving
[source]¶ An enumeration of data saving modes
- Members:
- NEVER = 0
- MOBILE = 1
- ALWAYS = 2
Data structures¶
-
class
tgvoip.
Stats
¶ Object storing call stats
-
bytes_sent_wifi
¶ Amount of data sent over WiFi :type:
int
-
bytes_sent_mobile
¶ Amount of data sent over mobile network :type:
int
-
bytes_recvd_wifi
¶ Amount of data received over WiFi :type:
int
-
bytes_recvd_mobile
¶ Amount of data received over mobile network :type:
int
-
-
class
tgvoip.
Endpoint
¶ Object storing endpoint info
Parameters: - _id (
int
) – Endpoint ID - ip (
str
) – Endpoint IPv4 address - ipv6 (
str
) – Endpoint IPv6 address - port (
int
) – Endpoint port - peer_tag (
bytes
) – Endpoint peer tag
- _id (
Utility functions¶
-
tgvoip.utils.
i2b
(value: int) → bytes[source]¶ Convert integer value to bytes
Parameters: value ( int
) – Value to convertReturns: Resulting bytes
object
-
tgvoip.utils.
b2i
(value: bytes) → int[source]¶ Convert bytes value to integer
Parameters: value ( bytes
) – Value to convertReturns: Resulting int
object
-
tgvoip.utils.
check_dhc
(g: int, p: int) → None[source]¶ Security checks for Diffie-Hellman prime and generator. Ported from Java implementation for Android
Parameters: - g (
int
) – DH generator - p (
int
) – DH prime
Raises: ValueError
if checks are not passed- g (
-
tgvoip.utils.
check_g
(g_x: int, p: int) → None[source]¶ Check g_ numbers
Parameters: - g_x – g_ number to check
- p – DH prime
Raises: ValueError
if checks are not passed
-
tgvoip.utils.
calc_fingerprint
(key: bytes) → int[source]¶ Calculate key fingerprint
Parameters: key ( bytes
) – Key to generate fingerprint forReturns: int
object representing a key fingerprint
-
tgvoip.utils.
generate_visualization
(key: Union[bytes, int], part2: Union[bytes, int]) -> (typing.List[str], typing.List[str])[source]¶ Generate emoji visualization of key
https://core.telegram.org/api/end-to-end/voice-calls#key-verification
Parameters: - key (
bytes
|int
) – Call auth key - part2 (
bytes
|int
) – g_a value of the caller
Returns: A tuple containing two lists (of emoji strings and of their text representations)
- key (