The Chirp C SDK does not implement any audio I/O layer to be as portable as possible in many different environments with all different kinds of audio hardware. As of April 2019, the Chirp C SDK is available for Embedded Linux, Raspberry Pi and many other Arm based platforms across a wide range of integrators. Our full supported platforms list can be found at the Developer Hub.

For a simple introduction to Chirp, we generally recommend using the Python SDK. The Python SDK will use Advanced Linux Sound Architecture under the hood to automatically process audio from the speaker and microphone. However in constrained environments such as Embedded Linux, efficient use of the resources is paramount, and using the C SDK directly can reduce CPU/memory usage when compared to the overheads introduced by Python.


Here we will run through how to set up the audio layer for Linux environments using ALSA, to provide the Chirp C SDK with audio input data. The same idea can be applied to ALSA's playback mode to output Chirp data. A more detailed run through of the PCM interface can be found at linuxjournal.com.

Chirp SDKs require mono audio data in float format, (methods to process shorts are also available). The main function below will configure ALSA in

  • SND_PCM_ASYNC - Asynchronous mode
  • SND_PCM_STREAM_CAPTURE - Capture stream to access microphone data
  • SND_PCM_FORMAT_FLOAT_LE - Audio input data is processed as floats
  • The default input device is used
  • A frame size of 0 is used, to instruct ALSA to determine the most appropriate frame size for the hardware.
/**------------------------------------------------------------------------
 *
 *  @file   alsa_driver.c
 *
 *  @brief  Chirp wrapper to interact with ALSA.
 *
 *-----------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>

#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>

static snd_pcm_t *handle = NULL;
static snd_pcm_hw_params_t *params = NULL;
static snd_async_handler_t *ahandler = NULL;
static snd_pcm_sframes_t period_size = 0;
static chirp_alsa_callback input_callback;

const char *device_name = "default";

typedef void (*alsa_input_callback)(char *buffer, size_t length);

static void
chirp_alsa_async_callback(snd_async_handler_t *ahandler)
{
    int rc;
    snd_pcm_t *handle = snd_async_handler_get_pcm(ahandler);
    snd_pcm_sframes_t available;

    size_t buffer_length = period_size * sizeof(float);
    char *buffer = (char *)malloc(buffer_length);

    available = snd_pcm_avail_update(handle);
    while (available >= period_size) {
        rc = snd_pcm_readi(handle, buffer, period_size);
        if (rc == -EPIPE) {
            fprintf(stderr, "overrun occurred\n");
            if ((rc = snd_pcm_prepare(handle)) < 0) {
                fprintf(stderr, "failed to prepare audio interface for use (%s)\n", snd_strerror(rc));
                exit(EXIT_FAILURE);
            }
        } else if (rc < 0) {
            fprintf(stderr, "error from read: %s\n", snd_strerror(rc));
            exit(EXIT_FAILURE);
        } else if (rc != (int)period_size) {
            fprintf(stderr, "short read, read %d frames\n", rc);
            exit(EXIT_FAILURE);
        }

        if (input_callback) {
            input_callback(buffer, buffer_length);
        }
        
        available = snd_pcm_avail_update(handle);
    }
    free(buffer);
}

int 
chirp_alsa_open(uint32_t sample_rate)
{
    int rc;
    
    if ((rc = snd_pcm_open(&handle, device_name, SND_PCM_STREAM_CAPTURE, SND_PCM_ASYNC)) < 0) {
        fprintf(stderr, "unable to open audio device %s (%s)\n", chirp_alsa->settings->device_name, snd_strerror(rc));
        exit(EXIT_FAILURE);
    }

    if ((rc = snd_pcm_hw_params_malloc(&params)) < 0) {
        fprintf(stderr, "failed to allocate hardware parameters (%s)\n", snd_strerror(rc));
        exit(EXIT_FAILURE);
    }

    if ((rc = snd_pcm_hw_params_any(handle, params)) < 0) {
        fprintf(stderr, "failed to initialise hardware parameters (%s)\n", snd_strerror(rc));
        exit(EXIT_FAILURE);
    }

    if ((rc = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
        fprintf(stderr, "failed to set access type (%s)\n", snd_strerror(rc));
        exit(EXIT_FAILURE);
    }

    if ((rc = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_FLOAT_LE)) < 0) {
        fprintf(stderr, "failed to set sample format (%s)\n", snd_strerror(rc));
        exit(EXIT_FAILURE);
    }

    if ((rc = snd_pcm_hw_params_set_channels(handle, params, 1)) < 0) {
        fprintf(stderr, "failed to set channel count (%s)\n", snd_strerror(rc));
        exit(EXIT_FAILURE);
    }

    if ((rc = snd_pcm_hw_params_set_rate_near(handle, params, &sample_rate, 0)) < 0) {
        fprintf(stderr, "failed to set sample rate (%s)\n", snd_strerror(rc));
        exit(EXIT_FAILURE);
    }

    if ((rc = snd_pcm_hw_params(handle, params)) < 0) {
        fprintf(stderr, "failed to set hardware parameters (%s)\n", snd_strerror(rc));
        exit(EXIT_FAILURE);
    }

    snd_pcm_uframes_t frames;
    if ((rc = snd_pcm_hw_params_get_period_size(params, &frames, 0)) < 0) {
        fprintf(stderr, "failed to get period size (%s)\n", snd_strerror(rc));
        exit(EXIT_FAILURE);
    }

    if ((rc = snd_async_add_pcm_handler(&ahandle, handle, chirp_alsa_async_callback, NULL)) < 0) {
        fprintf(stderr, "failed to add async callback (%s)\n", snd_strerror(rc));
        exit(EXIT_FAILURE);
    }

    period_size = frames;
    snd_pcm_hw_params_free(params);
}

extern int
chirp_alsa_set_input_callback(alsa_input_callback callback)
{
    input_callback = callback;
}

int
chirp_alsa_start(void)
{
    int rc;

    if ((rc = snd_pcm_start(handle)) < 0) {
        fprintf(stderr, "failed to start (%s)\n", snd_strerror(rc));
        exit(EXIT_FAILURE);
    }
}

int
chirp_alsa_close(void)
{
    if (handle != NULL) {
        snd_pcm_drain(handle);
        snd_pcm_close(handle);
        handle = NULL;
    }
}

/**-----------------------------------------------------------------------
 *
 *  @file alsa_driver.h
 *  @brief Header file for alsa_driver.c
 *
 *----------------------------------------------------------------------*/
#ifndef ALSA_DRIVER_H
#define ALSA_DRIVER_H

#include <stdint.h>

/**
 * Callback prototype for input audio data.
 * This is called when input data is processed.
 */
typedef void (*chirp_alsa_callback)(char *buffer, size_t length);

/**
 * Open the PCM device.
 *
 * @param sample_rate    The audio sample rate
 * @return               0 if no error or a negative value.
 */
extern int chirp_alsa_open(uint32_t sample_rate);

/**
 * Set the callback to call when input audio data is available
 *
 * @param callback       The callback function
 * @return               0 if no error or a negative value.
 */
extern int chirp_alsa_set_input_callback(alsa_input_callback callback);

/**
 * Start the PCM device. Must be called after `chirp_alsa_open`.
 *
 * @return        0 if no error or a negative value.
 */
extern int chirp_alsa_start(void);

/**
 * Close the PCM device.
 *
 * @return        0 if no error or a negative value.
 */
extern int chirp_alsa_close(void);

#endif

The audio layer is now complete. You can test out that data is being processed correctly by writing the buffer data in the callback to stdout.

rc = write(1, buffer, buffer_length);

Now we can add integrate Chirp in to the program, here is a quick example of how to do this in the main function. The on_input_callback function is passed to the ALSA driver and will be called once audio input data is available.

/**---------------------------------------------------------------------
 *
 *  @file    main.c
 *  @brief   Main function to set up Chirp
 *
 *--------------------------------------------------------------------*/

#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>

#include "alsa_driver.h"
#include "chirp_connect.h"

static chirp_connect_t *chirp = NULL;
static volatile bool program_running = true;

void signal_handler(int dummy) {
    program_running = false;
}

static void
on_input_callback(char *buffer, size_t length)
{
    chirp_connect_error_code_t chirprc;

    if (chirp != NULL) {
        chirprc = chirp_connect_process_input(chirp, (float *)buffer, length / sizeof(float));
        if (chirprc != CHIRP_CONNECT_OK) {
            fprintf(stderr, "%s\n", chirp_connect_error_code_to_string(chirprc));
        }
    }
}

static void
on_received_callback(void *ptr, uint8_t *payload, size_t length, uint8_t channel)
{
    if (payload) {
        printf("Received data\n");
    } else {
        printf("Decode failed\n");
    }
}

static void 
chirp_error_handler(chirp_connect_error_code_t rc)
{
	if (rc != CHIRP_CONNECT_OK)
	{
		const char *error_string = chirp_connect_error_code_to_string(rc);
		printf("Chirp error handler : %s\n", error_string);
		chirp_connect_free((void *) error_string);
		while(true);
	}
}

int
main (int argc, char *argv[])
{
    uint32_t sample_rate = 44100;
    chirp_alsa_open(sample_rate);
    chirp_alsa_start();
    
    chirp = new_chirp_connect(CHIRP_APP_KEY, CHIRP_APP_SECRET);
    if (chirp == NULL) {
        fprintf(stderr, "Error initialising the Chirp SDK\n");
        exit(-1);
    }

    chirp_connect_error_code_t rc = chirp_connect_set_config(chirp, CHIRP_APP_CONFIG);
    if (rc != CHIRP_CONNECT_OK)
		chirp_error_handler(rc);

    rc = chirp_connect_set_input_sample_rate(chirp, sample_rate);
    if (rc != CHIRP_CONNECT_OK)
		chirp_error_handler(rc);

    chirp_connect_callback_set_t callback_set = {
        .on_state_changed = NULL,
        .on_sending = NULL,
        .on_sent = NULL,
        .on_receiving = NULL,
        .on_received = on_received_callback
    };

    rc = chirp_connect_set_callbacks(chirp, callback_set);
    if (rc != CHIRP_CONNECT_OK)
		chirp_error_handler(rc);

    rc = chirp_connect_start(chirp);
    if (rc != CHIRP_CONNECT_OK)
		chirp_error_handler(rc);

    chirp_alsa_set_input_callback(on_input_callback);
    
    while(program_running) {
        sleep(1);
    }
    
    chirp_alsa_close();
    
    rc = chirp_connect_stop(chirp);
    if (rc != CHIRP_CONNECT_OK)
		chirp_error_handler(rc);

    del_chirp_connect(&chirp);
    chirp = NULL;
    
    return 0;
}

This quick example can be compiled using gcc with the following command.

gcc src/main.c src/alsa_driver.c -Iinc -lasound -lchirp-connect_linux-x86_64 -lm -o chirp_alsa

In the above code, output is simple printed to the console if data is received successfully or not. Here is where you begin your implementation of Chirp, whether it is used to activate a particular function of an IoT device, configure it with WiFi credentials, or to report back to the user with information from the device; the Chirp SDK is completely versatile to fit the needs of your data-over-sound application. You can read more about how Chirp works and how to use Chirp to its full potential here.

You can download the Chirp library for Arm processors straight from the Developer Hub. If you require builds for other platforms such as Linux and Raspberry Pi, please get in touch. Chirp is free to use for start ups and small businesses, so you can start prototyping your IoT projects right away.

joe@chirp.io