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
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>
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
342534static 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