Hello. In this article I will tell you how we made a sip-phone based on stm32f4-discovery using our embedded OS Embox . The characteristics of stm32f4-discovery (144MHz, 192Kb RAM, 1Mb ROM) may raise doubts about the possibility of such an implementation. We wondered if it would work out. As an answer, you can watch the video, and in the article itself - technical details.
How it works
The software of this device consists of a PJSIP soft-phone and the already mentioned Embox OS. PJSIP is responsible for sip protocol support, audio decoding. Embox provides the PJSIP standard library, pthread library, network stack, audio output drivers, and the rest of the OS features. For this, a POSIX interface is used, which makes it possible to transfer a wide class of applications from unix-like operating systems to embedded systems with virtually no changes. Transferring PJSIP to OS Embox was expectedly easy, but with the launch of PJSIP on a specific hardware platform, there were some difficulties. About them - next. ')
For those who want to repeat our success, we have prepared a small instruction. You will need stm32f4discovery + stm32dis-bb, a com-port connection to it (on the expansion card), a host with Linux, an ethernet connection between the card and the host.
You need to take github.com/embox/pjsip and go to this directory. Here are the build scripts, applications and files that we used to prepare for the demonstration.
The first simple step is to launch Embox on discovery. We use ELF as the image format. You can take a pre-assembled image, the embox file. Or you can build Embox yourself. For this:
target image is on the build / base / bin / embox path
After the firmware, you need to connect via the serial port to the discovery, connect the terminal emulator, set the settings to 115200 8n1. After launch, the system will display some status information, display and execute commands of the launch script, and provide a shell interface. Further it is supposed that all commands need to be executed in the discovery console.
If the default IP address does not suit you, you should execute
route del 192.168.1.0 dev eth0 ifconfig eth0 (ip-) netmask () up" route add () netmask () eth0
run pjsip_simpleua
Next, you need to build and run the host client. We return to the console on the host.
In order to build directly the host client, command
make
run
simpleua sip:test@(ip- )
Why is this?
We really wanted to do something unusual on a regular embedded platform. It was clear that the platform should be very popular and cheap, so we chose stm32f4-discovery - not an 8-bit controller, but not a gigahertz RaspberryPi or Beaglebone. They began to look at the discovery: in addition to ethernet, we noticed the possibility of sound output.
And then the stars came together: colleagues "on the shop floor" developed a sip-phone on OMAP137 with Linux. And to write a special software there was some kind of anguish: bitbake, who for a minute thinks about what and how to collect, recommendations to clean the project for any reason, and the first build takes about several hours.
Therefore, we have arranged for ourselves a challenge: a SIP-device, which is most similar in functionality to the phone, on a significantly weaker hardware.
An additional point in this section was the Indemsyscomment , in which he asked about software, which is only on Linux and not on the controllers (bare metal or classic RTOS).
Training
The first stage is the choice of third-party software to support sip-protocol and accompanying audio transmission. The first in the results of issuing google was www.pjsip.org , which knows everything it needs , allows processing with an ax and a file on a large scale, is portable and has low requirements for the amount of RAM (150kb claimed).
The PJSIP library will be just a module. The first commit commit on this topic. Here you can evaluate how the assembly description differs from the assembly for the host system (in any way) and what modifications are needed in PJSIP: disable positive applications for simplicity; We report about absence of SOCK_RDM.
In the future, these files certainly changed, but the basis remained the same. Additionally, macro values ​​were needed to reduce the amount of structures in memory, disable codecs, etc. The modern look of the descriptions can be estimated under the spoiler.
For the demonstration, we initially used an example called streamutil, which can transmit only the media stream.
After we added the build scripts and waved the file a bit, streamutil began to stop with a message about the missing device for sound output. There was no need to play from scratch, the manufacturer delivers demo applications for playing wav files from a flash drive. This code served as the basis for future implementation.
On the other hand, there was the question of which interface will be used for interaction: you can develop a sound plugin for PJSIP or use one of the existing ones for popular sound APIs. Among them was portaudio, noticeably simpler than, for example, the ALSA API. We have started implementing a driver with this interface.
In a nutshell, the interface is described as follows: register a callback, which will be called when necessary, when the sound card's buffer is close to empty. The remarkable simple API does not regulate the context in which the callback will be invoked, and in real systems a separate thread is started for this, which sleeps most of the time and performs the callback at the right time.
With such a scheme, problems like “leaky abstraction” arise: PJSIP clearly implies a separate stream. Attempts to call a callback on a signal led to a deadlock. It was necessary to do both in “adult” systems, with streams. At the end of this stage, the driver began to look like this .
In the future, we are going, if possible, to abandon the use of conventional streams in favor of light streams . The main difference of a light thread is the absence of its own stack, calculations are performed on the stack of the last thread executed. For easy streams, a pthread-compatible interface with available synchronization tools is provided. In theory, this will allow them to be used as an implementation of a portaudio stream, and thereby reduce memory consumption.
Lack of memory
The main problem we were struggling with was a lack of dynamic memory.
Here we need to make a remark about the dynamic selection in our system. We use pool'y - reserved areas of static memory for a fixed number of objects of a fixed size. The method is not the most flexible and sometimes makes writing some amount of boilerplate code, but in return we can control the memory consumption and catch the potential memory shortage at the compilation stage. In fact, configuring pool sizes is much easier than it might seem.
First, they are set in the global configuration file, where all other system parameters are described, such as: subsystems, commands, control policies, for example, the scheduler. The size of this file rarely exceeds 100 lines, although this number can strongly depend on the composition of the system.
Secondly, pool sizes are often the result of natural requirements, for example, in our stm32f4discovery there can be only 2 network adapters: onboard ethernet and loopback. Surprisingly, this certainty can be observed in other subsystems: the number of open files, the scheduler priorities, the number of irq handlers, and much more — all this can be easily analyzed in an embedded system.
Returning to PJSIP, it uses its own library for dynamic memory allocation, which, in turn, is implemented over standard malloc / free calls. Predicting the need for streamtuil in the amount of dynamic memory is quite difficult. But since the entire heap is exclusively used by streamutil, we can control its size and expand it with system pools.
One of the biggest pools in the system was the network packet buffer. Here I made a small hack: I changed the maximum packet size from standard 1514 bytes to 214, but I increased the maximum number of packets. The fact is that testing has shown: no more than 214 bytes go to / from streamutil, so this trick worked.
It took us several iterations of the heap increase, but after them the streamutil began to produce a clear sound.
Sip phone
It is clear that streamutil is a demo application, a little like the desired phone. It was necessary to take something more functional. A good candidate is pjsua, the reference sip-phone on PJSIP. Modified build scripts, started to run. Not enough memory. Okay, they've gotten pool sizes even tighter. Not enough memory. Here we began to torment vague doubts, and is it possible in principle to satisfy the requests of pjsua?
The first attempt to find out was to trace pjsua requests to malloc / free on the host. But, it turned out, the standard library is too actively using dynamic allocation for its own needs (for example, formatting a date into a string), so the trace output was heavily cluttered.
We went another way: in our standard library we do not use malloc / free, so the track will be much more informative. We added a trace to the memory allocation library, then we assembled the embox with pjsua for beagleboard and ran it on qemu. The results we were somewhat upset, it turned out a little more than 200kb only on a heap, without taking into account the fragmentation and static memory of pjsua. Such a volume in stm32f4 does not fit. There were two possible ways out: to seriously modify pjsua, or to look for an easier alternative.
Fortunately, there was an alternative - the simpleua demo application. Tracing dynamic allocation simpleua showed the maximum size of a live heap of about 110kb. If you add up with .data (~ 4kb) and .bss (~ 25kb), then there is enough space for the main stack and threads, the network packet buffer and the audio interface.
During the implementation, we had to add support for several regions of memory for the heap, increase the packet size (810 bytes are required) and reduce their number.
The result you saw on the video.
Conclusion
The article was written immediately after a successful launch. We did not have time to catch possible bugs, so if you meet them, you are warned. But seriously, we will welcome any feedback and comments. Thanks for attention!