alsa-stream.c¶
This is the source for the fromAudioDevice Stream implementation for ALSA, used for live audio capture on Linux.
Instructions¶
See live-spot-stream.c.
Code¶
Available in this TrulyNatural SDK installation at ~/Sensory/TrulyNaturalSDK/7.6.1/sample/c/src/alsa-stream.{c,h}
alsa-stream.h
/* Sensory Confidential
* Copyright (C)2016-2025 Sensory, Inc. https://sensory.com/
*
* TrulyHandsfree SDK custom stream header. See alsa-stream.c.
*------------------------------------------------------------------------------
*/
typedef enum {
STREAM_LATENCY_LOW, /* low latency, high CPU overhead */
STREAM_LATENCY_HIGH, /* higher latency, with lower CPU overhead */
} StreamLatency;
SnsrStream
streamFromALSA(const char *name, unsigned int rate,
SnsrStreamMode mode, StreamLatency latency);
alsa-stream.c
/* Sensory Confidential
* Copyright (C)2016-2025 Sensory, Inc. https://sensory.com/
*
* TrulyHandsfree SDK keyword spotting minimal example using a custom stream.
*------------------------------------------------------------------------------
* SnsrStream ALSA (Linux audio) provider implementation.
* Currently capture-only.
*------------------------------------------------------------------------------
*/
#include <snsr.h>
#include <alsa/asoundlib.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include "alsa-stream.h"
/* 15 ms at 16 kHz */
#define PERIOD_SIZE_LOW_LATENCY 240
/* 200 ms at 16 kHz */
#define PERIOD_SIZE_HIGH_LATENCY 3200
/* Minimum number of periods the buffer should include */
#define MIN_PERIOD_COUNT 5
/* Buffer size in ms */
#define MIN_BUFFER_MS 500
typedef struct {
snd_pcm_t *in;
const char *initErrorMsg; /* NULL if initialization was successful */
} ProviderData;
/* This wrapper macro is used to simplify ALSA library error checking.
* Commands are not executed if an error condition exists.
*/
#define AE(cmd)\
if (snsrStreamRC(b) == SNSR_RC_OK) {\
int r = snd_pcm_ ## cmd;\
if (r < 0) {\
snsrStream_setDetail(b, "ALSA error: %s", snd_strerror(r));\
snsrStream_setRC(b, SNSR_RC_ERROR);\
}\
}
static SnsrRC
streamOpen(SnsrStream b)
{
ProviderData *d = (ProviderData *)snsrStream_getData(b);
if (!d->in) {
if (d->initErrorMsg) snsrStream_setDetail(b, "%s", d->initErrorMsg);
else snsrStream_setDetail(b, "Could not open ALSA device for capture.");
return SNSR_RC_NOT_FOUND;
}
AE( prepare(d->in) );
return snsrStreamRC(b);
}
static SnsrRC
streamClose(SnsrStream b)
{
ProviderData *d = (ProviderData *)snsrStream_getData(b);
AE( drop(d->in) );
return snsrStreamRC(b);
}
static void
streamRelease(SnsrStream b)
{
ProviderData *d = (ProviderData *)snsrStream_getData(b);
AE( close(d->in) );
free((void *)d->initErrorMsg);
free(d);
}
static size_t
streamRead(SnsrStream b, void *buffer, size_t size)
{
ProviderData *d = (ProviderData *)snsrStream_getData(b);
snd_pcm_uframes_t read, total = 0, want = size / sizeof(short);
short *sbuff = buffer;
if (snd_pcm_state(d->in) == SND_PCM_STATE_XRUN) {
snsrStream_setRC(b, SNSR_RC_BUFFER_OVERRUN);
return 0;
}
do {
read = snd_pcm_readi(d->in, sbuff + total, want - total);
if ((int)read < 0) read = snd_pcm_recover(d->in, read, 0);
if ((int)read < 0) {
snsrStream_setDetail(b, "ALSA read error: %s", snd_strerror((int)read));
snsrStream_setRC(b, SNSR_RC_ERROR);
return 0;
}
total += read;
} while (total < want);
return total * sizeof(short);
}
static SnsrStream_Vmt ProviderDef = {
"ALSA",
&streamOpen, &streamClose, &streamRelease, &streamRead, NULL
};
SnsrStream
streamFromALSA(const char *name, unsigned int rate,
SnsrStreamMode mode, StreamLatency latency)
{
SnsrStream b;
ProviderData *d = (ProviderData *)malloc(sizeof(*d));
snd_pcm_t *h = NULL;
snd_pcm_hw_params_t *p = NULL;
int dir = 0;
snd_pcm_uframes_t frames;
if (!d) return NULL;
memset(d, 0, sizeof(*d));
b = snsrStream_alloc(&ProviderDef, d, 1, 0);
if (!b) {
free(d);
return NULL;
}
if (mode != SNSR_ST_MODE_READ) {
snsrStream_setRC(b, SNSR_RC_INVALID_MODE);
return b;
}
AE( open(&h, name, SND_PCM_STREAM_CAPTURE, 0) );
AE( hw_params_malloc(&p) );
AE( hw_params_any(h, p) );
AE( hw_params_set_access(h, p, SND_PCM_ACCESS_RW_INTERLEAVED) );
AE( hw_params_set_format(h, p, SND_PCM_FORMAT_S16_LE) );
AE( hw_params_set_channels(h, p, 1) );
AE( hw_params_set_rate(h, p, (unsigned)rate, 0) );
switch (latency) {
case STREAM_LATENCY_LOW: frames = PERIOD_SIZE_LOW_LATENCY; break;
case STREAM_LATENCY_HIGH: frames = PERIOD_SIZE_HIGH_LATENCY; break;
}
AE( hw_params_set_period_size_near(h, p, &frames, &dir) );
AE( hw_params_get_period_size(p, &frames, &dir) );
frames = MIN_PERIOD_COUNT * frames;
if (frames < MIN_BUFFER_MS * rate / 1000.0 )
frames *= (int)(MIN_BUFFER_MS * rate / 1000.0 / frames + 0.5);
AE( hw_params_set_buffer_size_near(h, p, &frames) );
AE( hw_params(h, p) );
snd_pcm_hw_params_free(p);
if (snsrStreamRC(b) == SNSR_RC_OK) d->in = h;
else if (h) snd_pcm_close(h);
if (!d->in) d->initErrorMsg = strdup(snsrStreamErrorDetail(b));
return b;
}