DFSI (or Digital Fixed Station Interface) is a protocol for digital communication with a /base station/ – typically a radio receiver/transmitter that serves as the hub of the local radio communication network. This article will cover interesting aspects and details of implementation of DFSI based on a real project.
DFSI functions over UDP/IP, using UDP packets for data transit. There are two distinct and very different parts constituting the whole protocol: control messages and voice conveyance messages. While control messages basically just package several variables together (such as protocol version, message type, correlation tag, etc.), voice conveyance messages can be viewed as a kind of VoIP protocol, since their main purpose is a sound transmission. They are very similar to a basic RTP and may even be compatible with it. Shown below is the example of how a voice conveyance packet carrying PCM samples looks like:
Everything up to SSRC is defined by RTP, and later headers can be determined by particular application. So in this regard DFSI voice conveyance is, in fact, RTP compatible.
Detailed DFSI description is accessible as TIA-102.BAHA standard.
Control message service did not cause us much trouble in this case. The idea behind is very simple: one message is represented as one UDP packet, and messages never exceed couple dozen bytes in size. Each message has it’s own packet type of predefined format, so no need for complex logic arises – simply put data X at offset Y and you’re good to go.
One peculiarity about the DFSI protocol is a heartbeat procedure. Once a connection is established, a special HEARTBEAT message should be sent both by the host and the connected base station, once in a specified time period (that might actually be different for host and base station). After several HEARTBEAT messages are missed, connection is considered to be lost.
There’s also a custom ACK message defined, and each message except ACK and HEARTBEAT is mandatorily ACK’ed. Once ACK is missed several times in a row, connection is considered to be down, the same as with HEARTBEAT.
For the proper implementation of HEARTBEAT and ACK, one employed hardware clock to measure time. As C++ was used, the most obvious choice was std::chrono.
The algorithm, rather simple, is as follows:
- After sending the message, store the time point;
- In main loop, subtract previously stored time point from current time.
- If result is greater than the specified delay, send a message again and go to 1.
In this way, one loop can efficiently serve several different events, and the logic is transparent enough to be easily understood.
On the stage when it came to the voice conveyance implementation, several problems appeared.
DFSI allows usage of ‘analog’ and ‘digital’ sound, first being simple μ-law PCM in a package of format shown above. Per μ-law encoding, one sound sample has size of 1 byte, and 8 kHz sampling is used. As a packet can carry no less than 160 samples at once, the timeframe that the packet covers is easily calculated to be no less than 20ms.
But ALSA driver that was used for recording used buffer of 1 second by default. That means that 20ms chunks of sound were available only after a second-long delay of silence; then all 50 packets would be sent at once.
One way to accommodate for this was, to use hardware clock via std::chrono, and time packets to have a delay between them of exactly 20ms. Another way was to initialize ALSA device differently. Though ALSA documentation is notorious for it’s complexity and unreadability, it was managed to discover the needed parameters: buffer_size and period_size, set with functions snd_pcm_hw_params_set_buffer_size_near() and snd_pcm_hw_params_set_period_size_near(), respectively.
For this configuration, buffer size of 1600 bytes and period size of 160 bytes worked out good enough.
In the end, though, both approaches were used simultaneously, since that way the service is most reliable and robust.