Skip to content

Commit 9411d05

Browse files
wov_capture: add plain-PCM WOV capture path
Some topologies expose WOV keyword capture as a plain PCM device (pcmC*D*c) instead of a compress node. Add a raw-ioctl PCM backend & auto-detect it when compress node is missing, behind the existing wov_dev wrapper. add -2 to capture 2 channels (default stays 1ch). Usage: wov_capture_app -c 0 -d 11 -o /tmp -v Signed-off-by: Naveen Manohar <naveen.m@intel.com>
1 parent a614db5 commit 9411d05

1 file changed

Lines changed: 219 additions & 24 deletions

File tree

tools/wov_capture/wov_capture_app.c

Lines changed: 219 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
//
33
// WOV (Wake-on-Voice) capture daemon for SOF firmware.
44
//
5-
// Opens a compress PCM device and loops forever capturing keyword-triggered
6-
// audio. Each trigger writes a time-stamped WAV file. The VAD gate kcontrol
7-
// is polled to detect silence and re-arm the pipeline without closing the
8-
// compress device.
5+
// Opens the WOV capture device (compress or plain-PCM, auto-detected) and
6+
// loops forever capturing keyword-triggered audio to time-stamped WAV
7+
// files. The VAD gate kcontrol is polled to detect silence and re-arm.
98
//
109
// Uses:
1110
// tinycompress - compress PCM device API
@@ -18,6 +17,8 @@
1817
#include <fcntl.h>
1918
#include <getopt.h>
2019
#include <inttypes.h>
20+
#include <limits.h>
21+
#include <poll.h>
2122
#include <signal.h>
2223
#include <stdarg.h>
2324
#include <stdbool.h>
@@ -51,6 +52,8 @@
5152
#define DRAIN_IDLE_MS 600 /* silence after last bytes → drain done */
5253
#define DRAIN_TIMEOUT_MS 6000 /* hard timeout for drain */
5354
#define CTL_DEV_FMT "/dev/snd/controlC%u"
55+
#define COMPR_DEV_FMT "/dev/snd/comprC%uD%u"
56+
#define PCM_DEV_FMT "/dev/snd/pcmC%uD%uc"
5457

5558
/* -------------------------------------------------------------------------
5659
* VAD kcontrol numids (pipeline 100 topology)
@@ -330,6 +333,195 @@ static struct compress *compress_setup(const struct wov_state *s)
330333
return c;
331334
}
332335

336+
/* -------------------------------------------------------------------------
337+
* Plain PCM device setup (raw ioctls, no libasound dependency)
338+
*
339+
* Some WOV topologies use a normal PCM capture node (pcmC*D*c) instead of
340+
* a compress node; read() works the same way once hw/sw_params + PREPARE
341+
* are done, so the capture loop is unchanged.
342+
* ---------------------------------------------------------------------- */
343+
static void hw_params_any(struct snd_pcm_hw_params *p)
344+
{
345+
unsigned int n;
346+
347+
memset(p, 0, sizeof(*p));
348+
for (n = 0; n < sizeof(p->masks) / sizeof(p->masks[0]); n++)
349+
memset(&p->masks[n], 0xff, sizeof(struct snd_mask));
350+
for (n = 0; n < sizeof(p->intervals) / sizeof(p->intervals[0]); n++) {
351+
p->intervals[n].min = 0;
352+
p->intervals[n].max = UINT_MAX;
353+
}
354+
p->rmask = ~0u;
355+
p->info = ~0u;
356+
}
357+
358+
static void hw_params_set_mask(struct snd_pcm_hw_params *p, int param, unsigned int bit)
359+
{
360+
struct snd_mask *m = &p->masks[param - SNDRV_PCM_HW_PARAM_FIRST_MASK];
361+
362+
memset(m, 0, sizeof(*m));
363+
m->bits[bit >> 5] |= (1u << (bit & 31));
364+
}
365+
366+
static void hw_params_set_int(struct snd_pcm_hw_params *p, int param, unsigned int val)
367+
{
368+
struct snd_interval *iv = &p->intervals[param - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
369+
370+
iv->min = iv->max = val;
371+
iv->openmin = iv->openmax = 0;
372+
iv->integer = 1;
373+
}
374+
375+
static int pcm_setup(const struct wov_state *s)
376+
{
377+
char path[64];
378+
snprintf(path, sizeof(path), PCM_DEV_FMT, s->card, s->device);
379+
380+
int fd = open(path, O_RDONLY);
381+
if (fd < 0) {
382+
log_err("open %s: %s", path, strerror(errno));
383+
return -1;
384+
}
385+
386+
struct snd_pcm_hw_params hw;
387+
hw_params_any(&hw);
388+
hw_params_set_mask(&hw, SNDRV_PCM_HW_PARAM_ACCESS, SNDRV_PCM_ACCESS_RW_INTERLEAVED);
389+
hw_params_set_mask(&hw, SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_FORMAT_S32_LE);
390+
hw_params_set_mask(&hw, SNDRV_PCM_HW_PARAM_SUBFORMAT, 0 /* SNDRV_PCM_SUBFORMAT_STD */);
391+
hw_params_set_int(&hw, SNDRV_PCM_HW_PARAM_CHANNELS, s->channels);
392+
hw_params_set_int(&hw, SNDRV_PCM_HW_PARAM_RATE, s->rate);
393+
hw_params_set_int(&hw, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, s->frag_sz);
394+
hw_params_set_int(&hw, SNDRV_PCM_HW_PARAM_PERIODS, s->fragments);
395+
396+
if (ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &hw) < 0) {
397+
log_err("pcm hw_params %s: %s", path, strerror(errno));
398+
close(fd);
399+
return -1;
400+
}
401+
402+
struct snd_pcm_sw_params sw = { 0 };
403+
sw.tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
404+
sw.period_step = 1;
405+
sw.avail_min = s->frag_sz / (s->channels * 4);
406+
sw.start_threshold = 1;
407+
sw.stop_threshold = UINT_MAX;
408+
409+
if (ioctl(fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sw) < 0) {
410+
log_err("pcm sw_params %s: %s", path, strerror(errno));
411+
close(fd);
412+
return -1;
413+
}
414+
415+
if (ioctl(fd, SNDRV_PCM_IOCTL_PREPARE) < 0) {
416+
log_err("pcm prepare %s: %s", path, strerror(errno));
417+
close(fd);
418+
return -1;
419+
}
420+
421+
return fd;
422+
}
423+
424+
/* -------------------------------------------------------------------------
425+
* Generic WOV device: dispatches to compress or plain-PCM backend
426+
* ---------------------------------------------------------------------- */
427+
enum wov_dev_mode {
428+
WOV_DEV_COMPRESS,
429+
WOV_DEV_PCM,
430+
};
431+
432+
struct wov_dev {
433+
enum wov_dev_mode mode;
434+
struct compress *compress; /* mode == WOV_DEV_COMPRESS */
435+
int pcm_fd; /* mode == WOV_DEV_PCM */
436+
};
437+
438+
/* Auto-detect: prefer the compress node if present, else fall back to PCM */
439+
static bool compr_node_exists(const struct wov_state *s)
440+
{
441+
char path[64];
442+
snprintf(path, sizeof(path), COMPR_DEV_FMT, s->card, s->device);
443+
return access(path, F_OK) == 0;
444+
}
445+
446+
static bool wov_dev_open(struct wov_dev *dev, const struct wov_state *s)
447+
{
448+
if (compr_node_exists(s)) {
449+
dev->mode = WOV_DEV_COMPRESS;
450+
dev->compress = compress_setup(s);
451+
return dev->compress != NULL;
452+
}
453+
454+
dev->mode = WOV_DEV_PCM;
455+
dev->pcm_fd = pcm_setup(s);
456+
return dev->pcm_fd >= 0;
457+
}
458+
459+
static const char *wov_dev_name(const struct wov_dev *dev)
460+
{
461+
return dev->mode == WOV_DEV_COMPRESS ? "compress" : "pcm";
462+
}
463+
464+
static int wov_dev_start(struct wov_dev *dev)
465+
{
466+
if (dev->mode == WOV_DEV_COMPRESS) {
467+
if (compress_start(dev->compress) != 0) {
468+
log_err("compress_start: %s", compress_get_error(dev->compress));
469+
return -1;
470+
}
471+
return 0;
472+
}
473+
474+
if (ioctl(dev->pcm_fd, SNDRV_PCM_IOCTL_START) < 0) {
475+
log_err("pcm start: %s", strerror(errno));
476+
return -1;
477+
}
478+
return 0;
479+
}
480+
481+
/* Returns 0 = data ready, -1 with errno=ETIME on timeout, -1 otherwise on error */
482+
static int wov_dev_wait(struct wov_dev *dev, int timeout_ms)
483+
{
484+
if (dev->mode == WOV_DEV_COMPRESS)
485+
return compress_wait(dev->compress, timeout_ms);
486+
487+
struct pollfd pfd = { .fd = dev->pcm_fd, .events = POLLIN };
488+
int ret = poll(&pfd, 1, timeout_ms);
489+
if (ret == 0) {
490+
errno = ETIME;
491+
return -1;
492+
}
493+
if (ret < 0)
494+
return -1;
495+
return 0;
496+
}
497+
498+
static int wov_dev_read(struct wov_dev *dev, void *buf, unsigned int sz)
499+
{
500+
if (dev->mode == WOV_DEV_COMPRESS)
501+
return compress_read(dev->compress, buf, sz);
502+
503+
int n = read(dev->pcm_fd, buf, sz);
504+
if (n < 0)
505+
log_err("pcm read: %s", strerror(errno));
506+
return n;
507+
}
508+
509+
static void wov_dev_stop(struct wov_dev *dev)
510+
{
511+
if (dev->mode == WOV_DEV_COMPRESS)
512+
compress_stop(dev->compress);
513+
else
514+
ioctl(dev->pcm_fd, SNDRV_PCM_IOCTL_DROP);
515+
}
516+
517+
static void wov_dev_close(struct wov_dev *dev)
518+
{
519+
if (dev->mode == WOV_DEV_COMPRESS)
520+
compress_close(dev->compress);
521+
else
522+
close(dev->pcm_fd);
523+
}
524+
333525
/* -------------------------------------------------------------------------
334526
* One WOV capture cycle
335527
* ---------------------------------------------------------------------- */
@@ -340,7 +532,7 @@ enum cycle_result {
340532
};
341533

342534
static enum cycle_result run_cycle(const struct wov_state *s,
343-
struct compress *compress,
535+
struct wov_dev *dev,
344536
int cycle_id,
345537
uint64_t *bytes_out)
346538
{
@@ -372,16 +564,16 @@ static enum cycle_result run_cycle(const struct wov_state *s,
372564

373565
while (!s->stop) {
374566
/* Wait up to POLL_INTERVAL_MS for data, then check VAD status.
375-
* compress_wait returns 0=data_ready, -1=timeout(ETIME) or error. */
376-
int ret = compress_wait(compress, POLL_INTERVAL_MS);
567+
* wov_dev_wait returns 0=data_ready, -1=timeout(ETIME) or error. */
568+
int ret = wov_dev_wait(dev, POLL_INTERVAL_MS);
377569
if (ret < 0 && errno != ETIME) {
378-
log_err("compress_wait: %s", compress_get_error(compress));
570+
log_err("wov_dev_wait: %s", strerror(errno));
379571
result = CYCLE_ERROR;
380572
break;
381573
}
382574

383575
if (ret == 0) {
384-
int n = compress_read(compress, buf, s->frag_sz);
576+
int n = wov_dev_read(dev, buf, s->frag_sz);
385577
if (n > 0) {
386578
if (!triggered) {
387579
triggered = true;
@@ -499,13 +691,15 @@ static void usage(const char *prog)
499691
"Usage: %s [options]\n"
500692
"\n"
501693
" -c CARD sound card number (default %d)\n"
502-
" -d DEVICE compress PCM device (default %d)\n"
694+
" -d DEVICE compress or PCM device number (default %d)\n"
695+
" auto-detects compress vs. plain-PCM WOV node\n"
503696
" -n CYCLES number of WOV cycles, 0=unlimited (default 0)\n"
504697
" -o DIR output directory for WAV files (default %s)\n"
505698
" -t THRESHOLD VAD gate threshold in raw S32 energy units\n"
506699
" 0=always open (default), 300000000=lab ambient\n"
507700
" -r RATE sample rate Hz (default %d)\n"
508701
" -f FRAG_SZ fragment size bytes (default %d)\n"
702+
" -2 capture 2 channels instead of the default 1\n"
509703
" -v verbose debug logging\n"
510704
" -h this help\n"
511705
"\n"
@@ -541,7 +735,7 @@ int main(int argc, char *argv[])
541735
g_state = &s;
542736

543737
int opt;
544-
while ((opt = getopt(argc, argv, "c:d:n:o:t:r:f:vh")) != -1) {
738+
while ((opt = getopt(argc, argv, "c:d:n:o:t:r:f:2vh")) != -1) {
545739
switch (opt) {
546740
case 'c': s.card = (unsigned int)atoi(optarg); break;
547741
case 'd': s.device = (unsigned int)atoi(optarg); break;
@@ -550,6 +744,7 @@ int main(int argc, char *argv[])
550744
case 't': s.vad_threshold = atoi(optarg); break;
551745
case 'r': s.rate = (unsigned int)atoi(optarg); break;
552746
case 'f': s.frag_sz = (unsigned int)atoi(optarg); break;
747+
case '2': s.channels = 2; break;
553748
case 'v': s.verbose = 1; break;
554749
case 'h': usage(argv[0]); return 0;
555750
default: usage(argv[0]); return 1;
@@ -564,18 +759,18 @@ int main(int argc, char *argv[])
564759
s.card, s.device, s.rate, s.channels, s.frag_sz,
565760
s.max_cycles ? "limited" : "unlimited");
566761

567-
/* Open compress device once and keep open for all cycles */
568-
struct compress *compress = compress_setup(&s);
569-
if (!compress)
762+
/* Open the WOV device once (compress or plain-PCM, auto-detected)
763+
* and keep it open for all cycles */
764+
struct wov_dev dev = { 0 };
765+
if (!wov_dev_open(&dev, &s))
570766
return 1;
571-
log_state("compress: OPEN card=%u device=%u", s.card, s.device);
767+
log_state("%s: OPEN card=%u device=%u", wov_dev_name(&dev), s.card, s.device);
572768

573-
if (compress_start(compress) != 0) {
574-
log_err("compress_start: %s", compress_get_error(compress));
575-
compress_close(compress);
769+
if (wov_dev_start(&dev) != 0) {
770+
wov_dev_close(&dev);
576771
return 1;
577772
}
578-
log_state("compress: RUNNING");
773+
log_state("%s: RUNNING", wov_dev_name(&dev));
579774

580775
/* Write initial VAD threshold if non-zero */
581776
if (s.vad_threshold != 0) {
@@ -598,7 +793,7 @@ int main(int argc, char *argv[])
598793
log_state("cycle %d: REARM", cycle);
599794

600795
uint64_t bytes = 0;
601-
enum cycle_result r = run_cycle(&s, compress, cycle, &bytes);
796+
enum cycle_result r = run_cycle(&s, &dev, cycle, &bytes);
602797
total_b += bytes;
603798

604799
if (r == CYCLE_ERROR) {
@@ -617,10 +812,10 @@ int main(int argc, char *argv[])
617812
usleep(300000);
618813
}
619814

620-
compress_stop(compress);
621-
log_state("compress: STOP");
622-
compress_close(compress);
623-
log_state("compress: CLOSE");
815+
wov_dev_stop(&dev);
816+
log_state("%s: STOP", wov_dev_name(&dev));
817+
wov_dev_close(&dev);
818+
log_state("%s: CLOSE", wov_dev_name(&dev));
624819

625820
log_info("done: %d cycle(s), %" PRIu64 " bytes total", cycle, total_b);
626821
return 0;

0 commit comments

Comments
 (0)