In this article we will discuss some of the architectural decisions behind the Chirp software stack, and how this allows us to maintain a suite of SDKs across many different platforms.
- SDK onion architecture
- API scalability
- Automation and workflows
Chirp’s mission is to enable seamless connectivity between any device with audio hardware. This means that we need to offer support for a wide range of platforms, from mobile applications to embedded microcontrollers. To achieve this with a small engineering team, we adopt an “onion” architecture (see below). This multi-layered approach creates an efficient development pipeline, simplifies maintenance, and makes the same high-performance Chirp technology available across all of our SDKs.
At the heart is Chirp’s core encode/decode technology. Originally spun out of a UCL research project, this has now undergone eight years of research-driven development and real-world testing across a multitude of different acoustic environments. These range from anechoic chambers to lecture theatres, from the noisy streets of New Delhi to vast sports stadiums.
On the next layer is the C SDK, which provides a lightweight public interface to the inner workings of the core. Exposing functions to process audio, send data and offering callbacks to receive data. This SDK is the central point for all other SDKs, so it is heavily supported by unit tests and memory leak detection. The C SDK can be used directly on many different low level devices and operating systems (see our supported platforms page for more information).
To ease the integration of Chirp even further, we also release higher-level SDKs where all audio processing is handled internally.
These SDKs bring the full power of Chirp to developers in as little as several lines of code. Each high level SDK is just a wrapper around the C SDK, using the appropriate native audio processing APIs to abstract the complexities of audio handling away from the developer. To call the C functions directly on each platform, we use foreign function interfaces such as JNI for the Android SDK,and ctypes for the Python SDK.
To keep the upgrade process streamlined, we try and adopt a unified interface across all of the SDKs. You can expect to see the same method names across all of the Chirp SDKs. The only exception being when native attributes are more appropriate such as data types. For example, all Chirp SDKs transfer data as an array of bytes, however, each SDK will use the appropriate native type corresponding to this.
On top of this we also extend our mobile support to include cross platform solutions such as React Native and Flutter. Although these are not officially supported platforms, the projects are open source and aim to be supported by the Chirp developer community.
To reduce the workload on the humans in the office, we try and automate tasks wherever possible, using a combination of Ansible and Jenkins to create a CI/CD pipeline. Unit tests are executed whenever a branch is pushed to GitHub, and in most cases pull requests will be blocked from being merged until all tests are passing.
The C SDK is built for all different architectures when changes are pushed to a release branch, and members of the engineering team are notified. Similarly, our Web/API projects are deployed to staging/production environments when merged to develop/master branches respectively.
As is common practice in software engineering, the Chirp architecture attempts to adhere to the DRY and KISS principles. Offloading some of the more tedious tasks to our build system, using effective workflows and a layered architecture allows us to spend more time doing the engineering work we all enjoy.