Skip to content

Commit 2a1c8ea

Browse files
committed
tools/wov_capture: add WOV slot/trigger helpers and dynamic control discovery
Extend WOV capture tooling: - wov_capture_app: add dynamic ALSA control lookup by name (ctl_find_numid), channel count CLI flag (-C), and direct capture mode. - read_wov_trigger.py: script to read wov_trigger_id active detection slot. - set_wov_slot.py: script to write slot assignment via IPC4 bytes kcontrol. Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
1 parent e298b39 commit 2a1c8ea

3 files changed

Lines changed: 149 additions & 27 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env python3
2+
# Reads wov_trigger_id to get active_slot after detection.
3+
# Usage: read_wov_trigger.py [numid] [card]
4+
import fcntl, os, struct, sys
5+
6+
numid = int(sys.argv[1]) if len(sys.argv) > 1 else 19
7+
card = int(sys.argv[2]) if len(sys.argv) > 2 else 0
8+
9+
SNDRV_CTL_IOCTL_TLV_READ = (2 << 30) | (8 << 16) | (0x55 << 8) | 0x1a
10+
11+
def read_active_slot(card_id=0, ctl_numid=19):
12+
response_size = 44
13+
buf = bytearray(struct.pack('<II', ctl_numid, response_size) + bytes(response_size))
14+
dev_path = f'/dev/snd/controlC{card_id}'
15+
fd = os.open(dev_path, os.O_RDWR)
16+
try:
17+
fcntl.ioctl(fd, SNDRV_CTL_IOCTL_TLV_READ, buf)
18+
finally:
19+
os.close(fd)
20+
return struct.unpack_from('<I', buf, 48)[0]
21+
22+
try:
23+
slot = read_active_slot(card, numid)
24+
print(f'active_slot = {slot} (255 = no active detection)')
25+
except Exception as e:
26+
print(f'Error reading trigger ID (numid={numid}, card={card}): {e}')
27+
sys.exit(1)

tools/wov_capture/set_wov_slot.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env python3
2+
# Usage: set_wov_slot.py <numid> <slot_id> [card]
3+
# numid: ALSA control numid for wov_init_1NN
4+
# slot_id: 0-2 to enable; 255 to disable (WOV_SLOT_INVALID)
5+
# card: card number (default 0)
6+
import sys, fcntl, struct, os
7+
8+
if len(sys.argv) < 3:
9+
print(f"Usage: {sys.argv[0]} <numid> <slot_id> [card]")
10+
sys.exit(1)
11+
12+
numid = int(sys.argv[1])
13+
slot_id = int(sys.argv[2])
14+
card = int(sys.argv[3]) if len(sys.argv) > 3 else 0
15+
16+
SOF_IPC4_ABI_MAGIC = 0x34464F53
17+
SOF_CTRL_CMD_BINARY = 3
18+
SOF_IPC4_BYTES_CONTROL_PARAM_ID = 202
19+
SNDRV_CTL_IOCTL_TLV_WRITE = (1 << 30) | (8 << 16) | (0x55 << 8) | 0x1b
20+
21+
payload_size = 4
22+
sizeof_abi_hdr = 32
23+
inner_length = sizeof_abi_hdr + payload_size # 36
24+
outer_length = 8 + inner_length # 44
25+
26+
abi_hdr = (struct.pack('<IIII', SOF_IPC4_ABI_MAGIC,
27+
SOF_IPC4_BYTES_CONTROL_PARAM_ID, payload_size, 0)
28+
+ bytes(16))
29+
payload = struct.pack('<I', slot_id)
30+
31+
ioctl_buf = (struct.pack('<II', numid, outer_length) +
32+
struct.pack('<II', SOF_CTRL_CMD_BINARY, inner_length) +
33+
abi_hdr + payload)
34+
35+
dev_path = f'/dev/snd/controlC{card}'
36+
fd = os.open(dev_path, os.O_RDWR)
37+
try:
38+
fcntl.ioctl(fd, SNDRV_CTL_IOCTL_TLV_WRITE, bytearray(ioctl_buf))
39+
print(f'OK: card={card} numid={numid} slot_id={slot_id}')
40+
except OSError as e:
41+
print(f'FAIL: card={card} numid={numid}: {e}')
42+
sys.exit(1)
43+
finally:
44+
os.close(fd)

tools/wov_capture/wov_capture_app.c

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
* ---------------------------------------------------------------------- */
4040
#define DEFAULT_CARD 0
4141
#define DEFAULT_DEVICE 11
42-
#define DEFAULT_CHANNELS 1
42+
#define DEFAULT_CHANNELS 2
4343
#define DEFAULT_RATE 16000
4444
#define DEFAULT_FRAGMENT_SZ 65536 /* bytes per fragment */
4545
#define DEFAULT_FRAGMENTS 8
@@ -157,17 +157,56 @@ static int ctl_open(unsigned int card)
157157
return fd;
158158
}
159159

160+
static int ctl_find_numid(unsigned int card, const char *name, int default_numid)
161+
{
162+
int fd = ctl_open(card);
163+
if (fd < 0)
164+
return default_numid;
165+
166+
struct snd_ctl_elem_list list;
167+
memset(&list, 0, sizeof(list));
168+
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &list) < 0 || list.count == 0) {
169+
close(fd);
170+
return default_numid;
171+
}
172+
173+
struct snd_ctl_elem_id *ids = calloc(list.count, sizeof(*ids));
174+
if (!ids) {
175+
close(fd);
176+
return default_numid;
177+
}
178+
list.space = list.count;
179+
list.pids = ids;
180+
181+
int numid = default_numid;
182+
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &list) >= 0) {
183+
for (unsigned int i = 0; i < list.used; i++) {
184+
if (strcmp((const char *)ids[i].name, name) == 0) {
185+
numid = ids[i].numid;
186+
break;
187+
}
188+
}
189+
}
190+
free(ids);
191+
close(fd);
192+
return numid;
193+
}
194+
160195
/* Read vad_gate_status_100: returns 0 on success, fills energy + active */
161196
static int vad_read_status(unsigned int card, uint32_t *energy, bool *active)
162197
{
198+
static int status_numid = -1;
199+
if (status_numid < 0)
200+
status_numid = ctl_find_numid(card, "vad_gate_status_100", VAD_STATUS_NUMID);
201+
163202
/* Buffer layout (56 bytes):
164203
* [0..7] outer tlv: numid, length=48
165204
* [8..15] inner tlv: filled by kernel
166205
* [16..47] sof_abi_hdr (32 bytes)
167206
* [48..55] status payload: energy(u32) + active(u8) + pad(3)
168207
*/
169208
uint32_t buf[14] = { 0 }; /* 56 bytes */
170-
buf[0] = VAD_STATUS_NUMID;
209+
buf[0] = status_numid;
171210
buf[1] = 8 + ABI_HDR_SIZE + VAD_STATUS_PAYLOAD_SZ; /* 48 */
172211

173212
int fd = ctl_open(card);
@@ -191,6 +230,8 @@ static int vad_read_status(unsigned int card, uint32_t *energy, bool *active)
191230
/* Write vad_gate_cfg_100 */
192231
static int vad_write_config(const struct wov_state *s)
193232
{
233+
int cfg_numid = ctl_find_numid(s->card, "vad_gate_cfg_100", VAD_CFG_NUMID);
234+
194235
/* Buffer layout (60 bytes):
195236
* [0..7] outer tlv: numid=CFG_NUMID, length=52
196237
* [8..15] inner tlv: SOF_CTRL_CMD_BINARY, length=44
@@ -201,7 +242,7 @@ static int vad_write_config(const struct wov_state *s)
201242

202243
/* outer tlv */
203244
uint32_t *u32 = (uint32_t *)buf;
204-
u32[0] = VAD_CFG_NUMID;
245+
u32[0] = cfg_numid;
205246
u32[1] = 8 + ABI_HDR_SIZE + VAD_CFG_PAYLOAD_SZ; /* 52 */
206247

207248
/* inner tlv */
@@ -380,27 +421,6 @@ static enum cycle_result run_cycle(const struct wov_state *s,
380421
break;
381422
}
382423

383-
if (ret == 0) {
384-
int n = compress_read(compress, buf, s->frag_sz);
385-
if (n > 0) {
386-
if (!triggered) {
387-
triggered = true;
388-
wav_path = wav_filename(s->out_dir, cycle_id);
389-
wav_file = wav_open(wav_path, s->channels, s->rate);
390-
if (!wav_file) {
391-
result = CYCLE_ERROR;
392-
break;
393-
}
394-
log_state("cycle %d: TRIGGERED → writing %s",
395-
cycle_id, wav_path);
396-
result = CYCLE_OK;
397-
}
398-
fwrite(buf, 1, n, wav_file);
399-
total_bytes += n;
400-
clock_gettime(CLOCK_REALTIME, &last_data_ts);
401-
}
402-
}
403-
404424
/* Poll VAD kcontrol */
405425
uint32_t energy = 0;
406426
bool active = false;
@@ -420,14 +440,43 @@ static enum cycle_result run_cycle(const struct wov_state *s,
420440
prev_active = active;
421441
first_status = false;
422442
}
423-
if (triggered && !silence_seen && !active) {
443+
if (!triggered) {
444+
if (s->vad_threshold == 0 || active || energy >= (uint32_t)s->vad_threshold) {
445+
triggered = true;
446+
wav_path = wav_filename(s->out_dir, cycle_id);
447+
wav_file = wav_open(wav_path, s->channels, s->rate);
448+
if (!wav_file) {
449+
result = CYCLE_ERROR;
450+
break;
451+
}
452+
log_state("cycle %d: TRIGGERED (%s, energy=%u) → writing %s",
453+
cycle_id, (s->vad_threshold == 0) ? "DIRECT" : (active ? "VAD_SPEECH" : "THRESHOLD"), energy, wav_path);
454+
result = CYCLE_OK;
455+
}
456+
}
457+
if (triggered && s->vad_threshold > 0 && !silence_seen && !active) {
424458
log_state("cycle %d: drain start (energy=%u)",
425459
cycle_id, energy);
426460
silence_seen = true;
427461
clock_gettime(CLOCK_REALTIME, &drain_start_ts);
428462
}
429463
}
430464

465+
if (ret == 0) {
466+
int n = compress_read(compress, buf, s->frag_sz);
467+
if (n > 0) {
468+
if (triggered && wav_file) {
469+
fwrite(buf, 1, n, wav_file);
470+
total_bytes += n;
471+
clock_gettime(CLOCK_REALTIME, &last_data_ts);
472+
if (s->vad_threshold == 0 && total_bytes >= (uint64_t)s->rate * s->channels * 4 * 4) {
473+
log_state("cycle %d: DIRECT_CAPTURE complete (%" PRIu64 " bytes)", cycle_id, total_bytes);
474+
break;
475+
}
476+
}
477+
}
478+
}
479+
431480
/* Drain completion check */
432481
if (silence_seen) {
433482
struct timespec now;
@@ -500,6 +549,7 @@ static void usage(const char *prog)
500549
"\n"
501550
" -c CARD sound card number (default %d)\n"
502551
" -d DEVICE compress PCM device (default %d)\n"
552+
" -C CHANNELS channels count (default %d)\n"
503553
" -n CYCLES number of WOV cycles, 0=unlimited (default 0)\n"
504554
" -o DIR output directory for WAV files (default %s)\n"
505555
" -t THRESHOLD VAD gate threshold in raw S32 energy units\n"
@@ -516,7 +566,7 @@ static void usage(const char *prog)
516566
"\n"
517567
"State log tags: STATE=pipeline transitions, CTL=kcontrol changes\n",
518568
prog,
519-
DEFAULT_CARD, DEFAULT_DEVICE, DEFAULT_OUT_DIR,
569+
DEFAULT_CARD, DEFAULT_DEVICE, DEFAULT_CHANNELS, DEFAULT_OUT_DIR,
520570
DEFAULT_RATE, DEFAULT_FRAGMENT_SZ,
521571
prog);
522572
}
@@ -541,10 +591,11 @@ int main(int argc, char *argv[])
541591
g_state = &s;
542592

543593
int opt;
544-
while ((opt = getopt(argc, argv, "c:d:n:o:t:r:f:vh")) != -1) {
594+
while ((opt = getopt(argc, argv, "c:d:C:n:o:t:r:f:vh")) != -1) {
545595
switch (opt) {
546596
case 'c': s.card = (unsigned int)atoi(optarg); break;
547597
case 'd': s.device = (unsigned int)atoi(optarg); break;
598+
case 'C': s.channels = (unsigned int)atoi(optarg); break;
548599
case 'n': s.max_cycles = atoi(optarg); break;
549600
case 'o': s.out_dir = optarg; break;
550601
case 't': s.vad_threshold = atoi(optarg); break;

0 commit comments

Comments
 (0)