• R/O
  • SSH

Repository summary

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

High performance Asterisk AudioSocket server implementation in asynchronous Python.


Recent Commits RSS

Rev. Time Author Message
cbeb3946c013 2023-03-10 03:12:14 Ssor tip Initial commit.

Recently edited Tags

Name Rev. Time Author
tip cbeb3946c013 2023-03-10 03:12:14 Ssor

Branches

Name Rev. Time Author Message
default cbeb3946c013 2023-03-10 03:12:14 Ssor Initial commit.

readme.md

Python AudioSocket Server

A high performance Asterisk AudioSocket server written in asynchronous Python.

AudioSocket Introduction

AudioSocket is a relatively new addition to the Asterisk IP-PBX project that allows external applications to easily access the raw audio (both read/write) of active calls, as opposed to audio only being available via RTP.

Such external applications act as a TCP server that Asterisk connects to after encountering an appropriate line of code in the dialplan. After, Asterisk will begin sending audio to the specified address/port (the encoding of this audio varies, as discussed in the tips section). This module is an implementation of such a TCP server, in Python.

Module Usage

Note: Formal documentation is provided below and in the module file itself, for real-world examples, see the the examples directory in the repository.

This Python module provides a clean and high performance interface for dealing with AudioSocket connections. To take advantage of the conventions established by the Python standard library, and to offer the best performance, this module is modeled around Python's concept of Protocols.

Underneath, this means that all connections are handled within a single thread, and I/O notification primitives provided by the underlying OS are used to signal when there is activity on any given connection, preventing the need to have an full OS thread per-connection. As for users of this module, this means all interaction with it occurs via two callbacks, and requires asyncio to be imported as well.

The entire module is implemented within a single file, audiosocket.py. To start using it, simply place the file somewhere your project can import it from. After importing it (import audiosocket) and asyncio, the server can be setup with two function calls - just like asyncio's own start_server - by invoking:

server = await audiosocket.start_server(<on_audio_callback>, <on_exception_callback>, <address>, <port>)`
await server.serve_forever()

start_server() behaves just like asyncio.start_server(), meaning the host, port and keyword arguments accepted by that function have the same behavior with this one.

The function callbacks should accept the following arguments:

on_audio_callback, called any time audio data from the connected peer is received. It is expected to be a function that accepts the following arguments: - uuid: The universally unique ID that identifies this specific call's audio, provided as a hexdecimal string.

- `peer_name`: A tuple consisting of the IP address and port number of the
               remote host the audio is being sent from.

- `audio`:     A `bytearray` instance containing the received audio data.
               An empty `bytearray` instance (`len(audio) == 0`)
               indicates the call hung up and no more audio will
               be received. Audio is either encoded in 8KHz, 16-bit
               mono PCM (when using the standalone dialplan applcation), or
               whatever audio codec was decided upon during call setup
               (when using Dial() application). If this argument is empty
               (has a length of 0), the call has been hung up and will not
               generate any more audio.

- Any extra keyword arguments are passed along to
  `asyncio.loop.create_server()`.

To send audio back to Asterisk, a bytes-like object must be returned by this callback. Audio must be sent back in chunks of 65,536 bytes or less (the size must be able to fit into a 16-bit unsigned integer). This audio must always be encoded as 8KHz, 16-bit mono PCM, regardless of the codec in use for the call.

Returning audiosocket.HANGUP_CALL_MESSAGE (or an empty bytearray instance) will request that the call represented by the value of the uuid parameter be hungup.

on_exception_callback, called any time an exception relating to the connected peer is raised. It is expected to be a function that accepts the following arguments: - uuid: The universally unique ID that identifies the specific call which caused the exception.

- `peer_name`: A tuple consisting of the IP address and port number of the
               remote host the exception-causing call came from.

- `error`:     An instance of the exception that occurred.

Tips

  • There are two ways to begin an AudioSocket connection within Asterisk, via a standalone dialplan application (AudioSocket(<uuid>,<address:port>)) or as a channel driver to Dial() (Dial(AudioSocket/<address:port>/<uuid>)). Using the standalone application will cause Asterisk to send the server 8KHz, 16-bit mono PCM (see Issues section). Using the Dial() application will cause Asterisk to send audio encoded in whatever codec was agreed upon during call setup (most commonly ULAW).

  • Since the programming model with this module is callback-based (which is not all that common), as opposed to making state that must persist between function calls global, a recommended approach is to make the callback functions methods in a class. That way instance variables can be used to reduce the scope of such variables, while still allowing the callback paradigm to work as intended.

Issues (as of my testing with Asterisk 18)

  • If the standalone dialplan application is used to initiate an AudioSocket connection, delivering PCM audio to th server, the CPU core/thread the connection within Asterisk is running on will reach 100% utilization (likely a runaway loop, which I will try to identify and fix/report to them).

  • If there is a wireless medium at any point in the network link between Asterisk and the AudioSocket server, the Asterisk module encounters strange read/write errors. This is a problem with the Asterisk module/Asterisk itself.