wmme-stream.c¶
This is the source for the fromAudioDevice Stream implementation for Windows Multimedia Extensions, used for live audio capture on Windows.
Instructions¶
See live-spot-stream.c.
Code¶
Available in this TrulyNatural SDK installation at ~/Sensory/TrulyNaturalSDK/7.6.1/sample/c/src/wmme-stream.{c,h}
wmme-stream.h
/* Sensory Confidential
* Copyright (C)2017-2025 Sensory, Inc. https://sensory.com/
*
* TrulyHandsfree SDK custom stream header. See wmme-stream.c.
*------------------------------------------------------------------------------
*/
typedef enum {
STREAM_LATENCY_LOW, /* low latency, high CPU overhead */
STREAM_LATENCY_HIGH, /* higher latency, with lower CPU overhead */
} StreamLatency;
SnsrStream
streamFromWMME(int devid, unsigned int rate,
SnsrStreamMode mode, StreamLatency latency);
wmme-stream.c
/* Sensory Confidential
* Copyright (C)2017-2025 Sensory, Inc. https://sensory.com/
*
*------------------------------------------------------------------------------
* SnsrStream provider for Windows Multimedia Extensions Waveform Audio.
* Currently capture-only.
*------------------------------------------------------------------------------
*/
#include <snsr.h>
#include <windows.h>
#include <mmsystem.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "wmme-stream.h"
/* Initial size of the circular capture buffer. 100 ms at 16kHz */
#define CAPTURE_MINSIZE 3200
/* Maximum size of the circular capture buffer. 10 s at 16kHz */
#define CAPTURE_MAXSIZE 320000
/* 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 3
/* Buffer size in ms */
#define MIN_BUFFER_MS 300
typedef struct {
SnsrStream capture; /* Captured audio buffer */
const char *initErrorMsg; /* NULL if initialization was successful */
HWAVEIN in; /* Capture handle */
DWORD msgThreadId; /* Messaging thread ID */
WAVEFORMATEX format; /* Audio format selector */
WAVEHDR **audioChunk; /* Audio buffers */
size_t chunks; /* number of allocated audio buffers */
UINT devId; /* Capture device ID */
CONDITION_VARIABLE captureNotEmpty;
CRITICAL_SECTION captureLock;
} ProviderData;
static void
setAudioError(SnsrStream stream, SnsrRC rc, MMRESULT r, const char *tag)
{
#define ERRMSG_SIZE 512
char errbuf[ERRMSG_SIZE];
char *errmsg = errbuf;
if (waveInGetErrorText(r, errmsg, ERRMSG_SIZE) == MMSYSERR_NOERROR) {
snsrStream_setDetail(stream, "%s: %s", tag, errmsg);
} else {
snsrStream_setDetail(stream, "%s: error code %i", r);
}
snsrStream_setRC(stream, rc);
}
static void
setLastError(SnsrStream b, const char *tag)
{
LPVOID lpMsgBuf;
DWORD r = GetLastError();
char *m;
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, r,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf, 0, NULL);
if (lpMsgBuf) {
m = (char *)lpMsgBuf;
m[strlen(m) - 2] = '\0';
snsrStream_setDetail(b, "%s error: %s", tag, m);
LocalFree(lpMsgBuf);
} else {
snsrStream_setDetail(b, "%s error #%i", tag, (int)r);
}
snsrStream_setRC(b, SNSR_RC_ERROR);
}
static void
audioAvailable(HWAVEIN hwi, WAVEHDR *h)
{
MMRESULT r = waveInUnprepareHeader(hwi, h, sizeof(*h));
ProviderData *d = (ProviderData *)h->dwUser;
size_t written;
EnterCriticalSection(&d->captureLock);
do {
if (r != MMSYSERR_NOERROR) {
setAudioError(d->capture, SNSR_RC_ERROR,
r, "waveInUnprepareHeader");
break;
}
if (!d->msgThreadId) break;
assert(d->format.nChannels == 1);
written = snsrStreamWrite(d->capture, h->lpData, 1, h->dwBytesRecorded);
if (written != h->dwBytesRecorded) {
snsrStream_setRC(d->capture, SNSR_RC_BUFFER_OVERRUN);
break;
}
r = waveInPrepareHeader(hwi, h, sizeof(*h));
if (r != MMSYSERR_NOERROR) {
setAudioError(d->capture, SNSR_RC_ERROR, r, "waveInPrepareHeader");
break;
}
r = waveInAddBuffer(hwi, h, sizeof(*h));
if (r != MMSYSERR_NOERROR) {
waveInUnprepareHeader(hwi, h, sizeof(*h));
setAudioError(d->capture, SNSR_RC_ERROR, r, "waveInAddBuffer");
}
} while (0);
LeaveCriticalSection(&d->captureLock);
WakeConditionVariable(&d->captureNotEmpty);
}
static DWORD WINAPI
threadProc(LPVOID lpParameter)
{
BOOL r;
MSG m;
while ((r = GetMessage(&m, (HWND)-1, 0, 0)) > 0) {
switch (m.message) {
case MM_WIM_DATA:
audioAvailable((HWAVEIN)m.wParam, (WAVEHDR *)m.lParam);
break;
case WM_QUIT:
case MM_WIM_CLOSE:
return ERROR_SUCCESS;
}
}
if (r < 0) return ERROR_INVALID_HANDLE;
return ERROR_SUCCESS;
}
/*------------------------------------------------------------------------------
*/
static SnsrRC
streamOpen(SnsrStream b)
{
ProviderData *d = (ProviderData *)snsrStream_getData(b);
HANDLE th;
MMRESULT r;
size_t i;
if (d->initErrorMsg) {
snsrStream_setDetail(b, "%s", d->initErrorMsg);
return SNSR_RC_NOT_FOUND;
}
snsrStreamOpen(d->capture);
assert(!d->msgThreadId);
th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)threadProc,
0, 0, &d->msgThreadId);
if (!th) {
setLastError(b, "CreateThread");
return snsrStreamRC(b);
}
CloseHandle(th);
do {
r = waveInOpen(&d->in, d->devId, &d->format,
(DWORD_PTR)d->msgThreadId, 0, CALLBACK_THREAD);
if (r != MMSYSERR_NOERROR) {
setAudioError(b, SNSR_RC_NOT_FOUND, r, "waveInOpen");
break;
}
for (i = 0; i < d->chunks; i++) {
r = waveInPrepareHeader(d->in, d->audioChunk[i],
sizeof(*d->audioChunk[i]));
if (r != MMSYSERR_NOERROR) {
setAudioError(b, SNSR_RC_ERROR, r, "waveInPrepareHeader");
break;
}
r = waveInAddBuffer(d->in, d->audioChunk[i],
sizeof(*d->audioChunk[i]));
if (r != MMSYSERR_NOERROR) {
setAudioError(b, SNSR_RC_ERROR, r, "waveInAddBuffer");
break;
}
}
r = waveInStart(d->in);
if (r != MMSYSERR_NOERROR)
setAudioError(b, SNSR_RC_ERROR, r, "waveInStart");
} while (0);
if (snsrStreamRC(b) != SNSR_RC_OK) {
if (d->in) {
waveInClose(d->in);
d->in = NULL;
}
if (d->msgThreadId) {
PostThreadMessage(d->msgThreadId, WM_QUIT, 0, 0);
d->msgThreadId = 0;
}
}
return snsrStreamRC(b);
}
static SnsrRC
streamClose(SnsrStream b)
{
ProviderData *d = (ProviderData *)snsrStream_getData(b);
size_t i;
/* Shut down the messaging thread. */
PostThreadMessage(d->msgThreadId, WM_QUIT, 0, 0);
d->msgThreadId = 0;
/* Unprepare all headers. */
waveInReset(d->in);
for (i = 0; i < d->chunks; i++) {
WAVEHDR *h = d->audioChunk[i];
while (waveInUnprepareHeader(d->in, h, sizeof(*h)) == WAVERR_STILLPLAYING)
Sleep(10);
}
while (waveInClose(d->in) == WAVERR_STILLPLAYING)
Sleep(10);
/* Flush the capture buffer */
snsrStreamSkip(d->capture, 1, CAPTURE_MAXSIZE);
snsrStream_setRC(d->capture, SNSR_RC_OK);
return snsrStreamRC(b);
}
static void
streamRelease(SnsrStream b)
{
ProviderData *d = (ProviderData *)snsrStream_getData(b);
size_t i;
snsrRelease(d->capture);
if (d->audioChunk) {
for (i = 0; i < d->chunks; i++) {
if (d->audioChunk[i]) free(d->audioChunk[i]->lpData);
free(d->audioChunk[i]);
}
free(d->audioChunk);
}
free((void *)d->initErrorMsg);
free(d);
}
static size_t
streamRead(SnsrStream b, void *buffer, size_t size)
{
SnsrRC r;
ProviderData *d = (ProviderData *)snsrStream_getData(b);
size_t read = 0;
EnterCriticalSection(&d->captureLock);
do {
read += snsrStreamRead(d->capture, (char *)buffer + read, 1, size - read);
r = snsrStreamRC(d->capture);
} while ((r == SNSR_RC_OK || r == SNSR_RC_EOF)
&& read < size
&& SleepConditionVariableCS(&d->captureNotEmpty,
&d->captureLock, INFINITE));
if (r != SNSR_RC_OK) {
snsrStream_setRC(b, r);
snsrStream_setDetail(b, "%s", snsrStreamErrorDetail(d->capture));
} else if (read < size) {
snsrStream_setRC(b, SNSR_RC_EOF);
}
LeaveCriticalSection(&d->captureLock);
return read;
}
static SnsrStream_Vmt ProviderDef = {
"WMME audio capture",
&streamOpen, &streamClose, &streamRelease, &streamRead, NULL
};
SnsrStream
streamFromWMME(int deviceId, unsigned int rate,
SnsrStreamMode mode, StreamLatency latency)
{
SnsrStream b;
ProviderData *d = (ProviderData *)malloc(sizeof(*d));
size_t chunkSize = 0, i;
if (!d) return NULL;
memset(d, 0, sizeof(*d));
b = snsrStream_alloc(&ProviderDef, d, 1, 0);
if (!b) {
free(d);
return NULL;
}
do {
d->devId = deviceId == -1? WAVE_MAPPER: (UINT)deviceId;
d->capture = snsrStreamFromBuffer(CAPTURE_MINSIZE, CAPTURE_MAXSIZE);
if (!d->capture) {
snsrStream_setRC(b, SNSR_RC_NO_MEMORY);
break;
}
snsrRetain(d->capture);
if (mode != SNSR_ST_MODE_READ) {
snsrStream_setRC(b, SNSR_RC_INVALID_MODE);
break;
}
/* Signalling and mutexes */
InitializeCriticalSection(&d->captureLock);
InitializeConditionVariable(&d->captureNotEmpty);
/* Prepare capture format description */
d->format.wFormatTag = WAVE_FORMAT_PCM;
d->format.wBitsPerSample = 16;
d->format.nChannels = 1;
d->format.nSamplesPerSec = rate;
d->format.nBlockAlign =
d->format.nChannels * d->format.wBitsPerSample / 8;
d->format.nAvgBytesPerSec =
d->format.nBlockAlign * d->format.nSamplesPerSec;
d->format.cbSize = 0;
/* Allocate buffers */
switch (latency) {
case STREAM_LATENCY_LOW: chunkSize = PERIOD_SIZE_LOW_LATENCY; break;
case STREAM_LATENCY_HIGH: chunkSize = PERIOD_SIZE_HIGH_LATENCY; break;
}
d->chunks = (int)(MIN_BUFFER_MS * rate / 1000.0 / chunkSize + 0.5);
if (d->chunks < MIN_PERIOD_COUNT) d->chunks = MIN_PERIOD_COUNT;
d->audioChunk = malloc(d->chunks * sizeof(*d->audioChunk));
if (!d->audioChunk) {
snsrStream_setRC(b, SNSR_RC_NO_MEMORY);
break;
}
memset(d->audioChunk, 0, d->chunks * sizeof(*d->audioChunk));
for (i = 0; i < d->chunks; i++) {
d->audioChunk[i] = malloc(sizeof(**d->audioChunk));
if (!d->audioChunk[i]) {
snsrStream_setRC(b, SNSR_RC_NO_MEMORY);
break;
}
memset(d->audioChunk[i], 0, sizeof(**d->audioChunk));
d->audioChunk[i]->dwBufferLength =
(DWORD)(chunkSize * sizeof(short) * d->format.nChannels);
d->audioChunk[i]->lpData = malloc(d->audioChunk[i]->dwBufferLength);
if (!d->audioChunk[i]->lpData) {
snsrStream_setRC(b, SNSR_RC_NO_MEMORY);
break;
}
d->audioChunk[i]->dwUser = (DWORD_PTR)d;
}
} while (0);
if (snsrStreamRC(b) != SNSR_RC_OK)
d->initErrorMsg = _strdup(snsrStreamErrorDetail(b));
return b;
}