1d4afb5ceSopenharmony_ci/*
2d4afb5ceSopenharmony_ci * alsa audio handling
3d4afb5ceSopenharmony_ci *
4d4afb5ceSopenharmony_ci * Written in 2010-2020 by Andy Green <andy@warmcat.com>
5d4afb5ceSopenharmony_ci *
6d4afb5ceSopenharmony_ci * This file is made available under the Creative Commons CC0 1.0
7d4afb5ceSopenharmony_ci * Universal Public Domain Dedication.
8d4afb5ceSopenharmony_ci */
9d4afb5ceSopenharmony_ci
10d4afb5ceSopenharmony_ci#include <libwebsockets.h>
11d4afb5ceSopenharmony_ci#include <string.h>
12d4afb5ceSopenharmony_ci#include <signal.h>
13d4afb5ceSopenharmony_ci#include <sys/types.h>
14d4afb5ceSopenharmony_ci#include <sys/stat.h>
15d4afb5ceSopenharmony_ci#include <fcntl.h>
16d4afb5ceSopenharmony_ci
17d4afb5ceSopenharmony_ci#include <alsa/asoundlib.h>
18d4afb5ceSopenharmony_ci#include <pv_porcupine.h>
19d4afb5ceSopenharmony_ci
20d4afb5ceSopenharmony_ci#include <mpg123.h>
21d4afb5ceSopenharmony_ci
22d4afb5ceSopenharmony_ci#include "private.h"
23d4afb5ceSopenharmony_ci
24d4afb5ceSopenharmony_ciextern struct lws_ss_handle *hss_avs_event, *hss_avs_sync;
25d4afb5ceSopenharmony_ci
26d4afb5ceSopenharmony_ciint
27d4afb5ceSopenharmony_ciavs_query_start(struct lws_context *context);
28d4afb5ceSopenharmony_ci
29d4afb5ceSopenharmony_cienum {
30d4afb5ceSopenharmony_ci	MODE_IDLE,
31d4afb5ceSopenharmony_ci	MODE_CAPTURING,
32d4afb5ceSopenharmony_ci	MODE_PLAYING
33d4afb5ceSopenharmony_ci};
34d4afb5ceSopenharmony_ci
35d4afb5ceSopenharmony_cistruct raw_vhd {
36d4afb5ceSopenharmony_ci	int16_t			p[8 * 1024]; /* 500ms at 16kHz 16-bit PCM */
37d4afb5ceSopenharmony_ci	pv_porcupine_object_t	*porc;
38d4afb5ceSopenharmony_ci	snd_pcm_t		*pcm_capture;
39d4afb5ceSopenharmony_ci	snd_pcm_t		*pcm_playback;
40d4afb5ceSopenharmony_ci	snd_pcm_hw_params_t	*params;
41d4afb5ceSopenharmony_ci	snd_pcm_uframes_t	frames;
42d4afb5ceSopenharmony_ci	int16_t			*porcbuf;
43d4afb5ceSopenharmony_ci
44d4afb5ceSopenharmony_ci	mpg123_handle		*mh;
45d4afb5ceSopenharmony_ci
46d4afb5ceSopenharmony_ci	mp3_done_cb		done_cb;
47d4afb5ceSopenharmony_ci	void			*opaque;
48d4afb5ceSopenharmony_ci
49d4afb5ceSopenharmony_ci	int			mode;
50d4afb5ceSopenharmony_ci	int			rate;
51d4afb5ceSopenharmony_ci
52d4afb5ceSopenharmony_ci	int			porc_spf;
53d4afb5ceSopenharmony_ci	int			filefd;
54d4afb5ceSopenharmony_ci	int			rpos;
55d4afb5ceSopenharmony_ci	int			wpos;
56d4afb5ceSopenharmony_ci	int			porcpos;
57d4afb5ceSopenharmony_ci	int			npos;
58d4afb5ceSopenharmony_ci	int			times;
59d4afb5ceSopenharmony_ci	int			quietcount;
60d4afb5ceSopenharmony_ci	int			anycount;
61d4afb5ceSopenharmony_ci
62d4afb5ceSopenharmony_ci	int			wplay;
63d4afb5ceSopenharmony_ci	int			rplay;
64d4afb5ceSopenharmony_ci
65d4afb5ceSopenharmony_ci	char			last_wake_detect;
66d4afb5ceSopenharmony_ci	char			destroy_mh_on_drain;
67d4afb5ceSopenharmony_ci};
68d4afb5ceSopenharmony_ci
69d4afb5ceSopenharmony_cistatic struct raw_vhd *avhd;
70d4afb5ceSopenharmony_ci
71d4afb5ceSopenharmony_ci/*
72d4afb5ceSopenharmony_ci * called from alexa.c to grab the next chunk of audio capture buffer
73d4afb5ceSopenharmony_ci * for upload
74d4afb5ceSopenharmony_ci */
75d4afb5ceSopenharmony_ci
76d4afb5ceSopenharmony_ciint
77d4afb5ceSopenharmony_cispool_capture(uint8_t *buf, size_t len)
78d4afb5ceSopenharmony_ci{
79d4afb5ceSopenharmony_ci	int16_t *sam = (int16_t *)buf;
80d4afb5ceSopenharmony_ci	size_t s, os;
81d4afb5ceSopenharmony_ci
82d4afb5ceSopenharmony_ci	if (avhd->mode != MODE_CAPTURING)
83d4afb5ceSopenharmony_ci		return -1;
84d4afb5ceSopenharmony_ci
85d4afb5ceSopenharmony_ci	os = s = len / 2;
86d4afb5ceSopenharmony_ci
87d4afb5ceSopenharmony_ci	while (s && avhd->wpos != avhd->npos) {
88d4afb5ceSopenharmony_ci		*sam++ = avhd->p[avhd->npos];
89d4afb5ceSopenharmony_ci		avhd->npos = (avhd->npos + 1)  % LWS_ARRAY_SIZE(avhd->p);
90d4afb5ceSopenharmony_ci		s--;
91d4afb5ceSopenharmony_ci	}
92d4afb5ceSopenharmony_ci
93d4afb5ceSopenharmony_ci	lwsl_info("Copied %d samples (%d %d)\n", (int)(os - s),
94d4afb5ceSopenharmony_ci			avhd->wpos, avhd->npos);
95d4afb5ceSopenharmony_ci
96d4afb5ceSopenharmony_ci	return (os - s) * 2;
97d4afb5ceSopenharmony_ci}
98d4afb5ceSopenharmony_ci
99d4afb5ceSopenharmony_ci/*
100d4afb5ceSopenharmony_ci * Called from alexa.c to control when the mp3 playback should begin and end
101d4afb5ceSopenharmony_ci */
102d4afb5ceSopenharmony_ci
103d4afb5ceSopenharmony_ciint
104d4afb5ceSopenharmony_ciplay_mp3(mpg123_handle *mh, mp3_done_cb cb, void *opaque)
105d4afb5ceSopenharmony_ci{
106d4afb5ceSopenharmony_ci	if (mh) {
107d4afb5ceSopenharmony_ci		avhd->mh = mh;
108d4afb5ceSopenharmony_ci		avhd->mode = MODE_PLAYING;
109d4afb5ceSopenharmony_ci		snd_pcm_prepare(avhd->pcm_playback);
110d4afb5ceSopenharmony_ci
111d4afb5ceSopenharmony_ci		return 0;
112d4afb5ceSopenharmony_ci	}
113d4afb5ceSopenharmony_ci
114d4afb5ceSopenharmony_ci	avhd->destroy_mh_on_drain = 1;
115d4afb5ceSopenharmony_ci	avhd->done_cb = cb;
116d4afb5ceSopenharmony_ci	avhd->opaque = opaque;
117d4afb5ceSopenharmony_ci
118d4afb5ceSopenharmony_ci	return 0;
119d4afb5ceSopenharmony_ci}
120d4afb5ceSopenharmony_ci
121d4afb5ceSopenharmony_ci/*
122d4afb5ceSopenharmony_ci * Helper used to set alsa hwparams on both capture and playback channels
123d4afb5ceSopenharmony_ci */
124d4afb5ceSopenharmony_ci
125d4afb5ceSopenharmony_cistatic int
126d4afb5ceSopenharmony_ciset_hw_params(struct lws_vhost *vh, snd_pcm_t **pcm, int type)
127d4afb5ceSopenharmony_ci{
128d4afb5ceSopenharmony_ci	unsigned int rate = pv_sample_rate(); /* it's 16kHz */
129d4afb5ceSopenharmony_ci	snd_pcm_hw_params_t *params;
130d4afb5ceSopenharmony_ci	lws_sock_file_fd_type u;
131d4afb5ceSopenharmony_ci	struct pollfd pfd;
132d4afb5ceSopenharmony_ci	struct lws *wsi1;
133d4afb5ceSopenharmony_ci	int n;
134d4afb5ceSopenharmony_ci
135d4afb5ceSopenharmony_ci	n = snd_pcm_open(pcm, "default", type, SND_PCM_NONBLOCK);
136d4afb5ceSopenharmony_ci	if (n < 0) {
137d4afb5ceSopenharmony_ci		lwsl_err("%s: Can't open default for playback: %s\n",
138d4afb5ceSopenharmony_ci			 __func__, snd_strerror(n));
139d4afb5ceSopenharmony_ci
140d4afb5ceSopenharmony_ci		return -1;
141d4afb5ceSopenharmony_ci	}
142d4afb5ceSopenharmony_ci
143d4afb5ceSopenharmony_ci	if (snd_pcm_poll_descriptors(*pcm, &pfd, 1) != 1) {
144d4afb5ceSopenharmony_ci		lwsl_err("%s: failed to get playback desc\n", __func__);
145d4afb5ceSopenharmony_ci		return -1;
146d4afb5ceSopenharmony_ci	}
147d4afb5ceSopenharmony_ci
148d4afb5ceSopenharmony_ci	u.filefd = (lws_filefd_type)(long long)pfd.fd;
149d4afb5ceSopenharmony_ci	wsi1 = lws_adopt_descriptor_vhost(vh, LWS_ADOPT_RAW_FILE_DESC, u,
150d4afb5ceSopenharmony_ci					  "lws-audio-test", NULL);
151d4afb5ceSopenharmony_ci	if (!wsi1) {
152d4afb5ceSopenharmony_ci		lwsl_err("%s: Failed to adopt playback desc\n", __func__);
153d4afb5ceSopenharmony_ci		goto bail;
154d4afb5ceSopenharmony_ci	}
155d4afb5ceSopenharmony_ci	if (type == SND_PCM_STREAM_PLAYBACK)
156d4afb5ceSopenharmony_ci		lws_rx_flow_control(wsi1, 0); /* no POLLIN */
157d4afb5ceSopenharmony_ci
158d4afb5ceSopenharmony_ci	snd_pcm_hw_params_malloc(&params);
159d4afb5ceSopenharmony_ci	snd_pcm_hw_params_any(*pcm, params);
160d4afb5ceSopenharmony_ci
161d4afb5ceSopenharmony_ci	n = snd_pcm_hw_params_set_access(*pcm, params,
162d4afb5ceSopenharmony_ci					 SND_PCM_ACCESS_RW_INTERLEAVED);
163d4afb5ceSopenharmony_ci	if (n < 0)
164d4afb5ceSopenharmony_ci		goto bail1;
165d4afb5ceSopenharmony_ci
166d4afb5ceSopenharmony_ci	n = snd_pcm_hw_params_set_format(*pcm, params, SND_PCM_FORMAT_S16_LE);
167d4afb5ceSopenharmony_ci	if (n < 0)
168d4afb5ceSopenharmony_ci		goto bail1;
169d4afb5ceSopenharmony_ci
170d4afb5ceSopenharmony_ci	n = snd_pcm_hw_params_set_channels(*pcm, params, 1);
171d4afb5ceSopenharmony_ci	if (n < 0)
172d4afb5ceSopenharmony_ci		goto bail1;
173d4afb5ceSopenharmony_ci
174d4afb5ceSopenharmony_ci	n = snd_pcm_hw_params_set_rate_near(*pcm, params, &rate, 0);
175d4afb5ceSopenharmony_ci	if (n < 0)
176d4afb5ceSopenharmony_ci		goto bail1;
177d4afb5ceSopenharmony_ci
178d4afb5ceSopenharmony_ci	lwsl_notice("%s: %s rate %d\n", __func__,
179d4afb5ceSopenharmony_ci		type == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture", rate);
180d4afb5ceSopenharmony_ci
181d4afb5ceSopenharmony_ci	n = snd_pcm_hw_params(*pcm, params);
182d4afb5ceSopenharmony_ci	snd_pcm_hw_params_free(params);
183d4afb5ceSopenharmony_ci	if (n < 0)
184d4afb5ceSopenharmony_ci		goto bail;
185d4afb5ceSopenharmony_ci
186d4afb5ceSopenharmony_ci	return 0;
187d4afb5ceSopenharmony_ci
188d4afb5ceSopenharmony_cibail1:
189d4afb5ceSopenharmony_ci	snd_pcm_hw_params_free(params);
190d4afb5ceSopenharmony_cibail:
191d4afb5ceSopenharmony_ci	lwsl_err("%s: Set hw params failed: %s\n", __func__, snd_strerror(n));
192d4afb5ceSopenharmony_ci
193d4afb5ceSopenharmony_ci	return -1;
194d4afb5ceSopenharmony_ci}
195d4afb5ceSopenharmony_ci
196d4afb5ceSopenharmony_ci/*
197d4afb5ceSopenharmony_ci * The lws RAW file protocol handler that wraps ALSA.
198d4afb5ceSopenharmony_ci *
199d4afb5ceSopenharmony_ci * The timing is coming from ALSA capture channel... since they are both set to
200d4afb5ceSopenharmony_ci * 16kHz, it's enough just to have the one.
201d4afb5ceSopenharmony_ci */
202d4afb5ceSopenharmony_ci
203d4afb5ceSopenharmony_cistatic int
204d4afb5ceSopenharmony_cicallback_audio(struct lws *wsi, enum lws_callback_reasons reason, void *user,
205d4afb5ceSopenharmony_ci	       void *in, size_t len)
206d4afb5ceSopenharmony_ci{
207d4afb5ceSopenharmony_ci	struct raw_vhd *vhd = (struct raw_vhd *)lws_protocol_vh_priv_get(
208d4afb5ceSopenharmony_ci				   lws_get_vhost(wsi), lws_get_protocol(wsi));
209d4afb5ceSopenharmony_ci	uint16_t rands[50];
210d4afb5ceSopenharmony_ci	int16_t temp[256];
211d4afb5ceSopenharmony_ci	bool det;
212d4afb5ceSopenharmony_ci	long avg;
213d4afb5ceSopenharmony_ci	int n, s;
214d4afb5ceSopenharmony_ci
215d4afb5ceSopenharmony_ci	switch (reason) {
216d4afb5ceSopenharmony_ci	case LWS_CALLBACK_PROTOCOL_INIT:
217d4afb5ceSopenharmony_ci
218d4afb5ceSopenharmony_ci		if (avhd) /* just on one vhost */
219d4afb5ceSopenharmony_ci			return 0;
220d4afb5ceSopenharmony_ci
221d4afb5ceSopenharmony_ci		avhd = vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
222d4afb5ceSopenharmony_ci				lws_get_protocol(wsi), sizeof(struct raw_vhd));
223d4afb5ceSopenharmony_ci
224d4afb5ceSopenharmony_ci		/*
225d4afb5ceSopenharmony_ci		 * Set up the wakeword library
226d4afb5ceSopenharmony_ci		 */
227d4afb5ceSopenharmony_ci
228d4afb5ceSopenharmony_ci		n = pv_porcupine_init("porcupine_params.pv", "alexa_linux.ppn",
229d4afb5ceSopenharmony_ci					1.0, &vhd->porc);
230d4afb5ceSopenharmony_ci		if (n) {
231d4afb5ceSopenharmony_ci			lwsl_err("%s: porcupine init fail %d\n", __func__, n);
232d4afb5ceSopenharmony_ci
233d4afb5ceSopenharmony_ci			return -1;
234d4afb5ceSopenharmony_ci		}
235d4afb5ceSopenharmony_ci		vhd->porc_spf = pv_porcupine_frame_length();
236d4afb5ceSopenharmony_ci		vhd->porcbuf = malloc(vhd->porc_spf * 2);
237d4afb5ceSopenharmony_ci		lwsl_info("%s: %s porc frame length is %d samples\n", __func__,
238d4afb5ceSopenharmony_ci				lws_get_vhost_name(lws_get_vhost(wsi)),
239d4afb5ceSopenharmony_ci				vhd->porc_spf);
240d4afb5ceSopenharmony_ci
241d4afb5ceSopenharmony_ci		vhd->rate = pv_sample_rate(); /* 16kHz */
242d4afb5ceSopenharmony_ci
243d4afb5ceSopenharmony_ci		/* set up alsa */
244d4afb5ceSopenharmony_ci
245d4afb5ceSopenharmony_ci		if (set_hw_params(lws_get_vhost(wsi), &vhd->pcm_playback,
246d4afb5ceSopenharmony_ci				  SND_PCM_STREAM_PLAYBACK))  {
247d4afb5ceSopenharmony_ci			lwsl_err("%s: Can't open default for playback\n",
248d4afb5ceSopenharmony_ci				 __func__);
249d4afb5ceSopenharmony_ci
250d4afb5ceSopenharmony_ci			return -1;
251d4afb5ceSopenharmony_ci		}
252d4afb5ceSopenharmony_ci
253d4afb5ceSopenharmony_ci		if (set_hw_params(lws_get_vhost(wsi), &vhd->pcm_capture,
254d4afb5ceSopenharmony_ci				  SND_PCM_STREAM_CAPTURE))  {
255d4afb5ceSopenharmony_ci			lwsl_err("%s: Can't open default for capture\n",
256d4afb5ceSopenharmony_ci				 __func__);
257d4afb5ceSopenharmony_ci
258d4afb5ceSopenharmony_ci			return -1;
259d4afb5ceSopenharmony_ci		}
260d4afb5ceSopenharmony_ci
261d4afb5ceSopenharmony_ci		snd_config_update_free_global();
262d4afb5ceSopenharmony_ci
263d4afb5ceSopenharmony_ci		break;
264d4afb5ceSopenharmony_ci
265d4afb5ceSopenharmony_ci	case LWS_CALLBACK_PROTOCOL_DESTROY:
266d4afb5ceSopenharmony_ci		lwsl_info("%s: LWS_CALLBACK_PROTOCOL_DESTROY\n", __func__);
267d4afb5ceSopenharmony_ci		if (!vhd)
268d4afb5ceSopenharmony_ci			break;
269d4afb5ceSopenharmony_ci
270d4afb5ceSopenharmony_ci		if (vhd->porcbuf) {
271d4afb5ceSopenharmony_ci			free(vhd->porcbuf);
272d4afb5ceSopenharmony_ci			vhd->porcbuf = NULL;
273d4afb5ceSopenharmony_ci		}
274d4afb5ceSopenharmony_ci		if (vhd->pcm_playback) {
275d4afb5ceSopenharmony_ci			snd_pcm_drop(vhd->pcm_playback);
276d4afb5ceSopenharmony_ci			snd_pcm_close(vhd->pcm_playback);
277d4afb5ceSopenharmony_ci			vhd->pcm_playback = NULL;
278d4afb5ceSopenharmony_ci		}
279d4afb5ceSopenharmony_ci		if (vhd->pcm_capture) {
280d4afb5ceSopenharmony_ci			snd_pcm_drop(vhd->pcm_capture);
281d4afb5ceSopenharmony_ci			snd_pcm_close(vhd->pcm_capture);
282d4afb5ceSopenharmony_ci			vhd->pcm_capture = NULL;
283d4afb5ceSopenharmony_ci		}
284d4afb5ceSopenharmony_ci		if (vhd->porc) {
285d4afb5ceSopenharmony_ci			pv_porcupine_delete(vhd->porc);
286d4afb5ceSopenharmony_ci			vhd->porc = NULL;
287d4afb5ceSopenharmony_ci		}
288d4afb5ceSopenharmony_ci
289d4afb5ceSopenharmony_ci		/* avoid most of the valgrind mess from alsa */
290d4afb5ceSopenharmony_ci		snd_config_update_free_global();
291d4afb5ceSopenharmony_ci
292d4afb5ceSopenharmony_ci		break;
293d4afb5ceSopenharmony_ci
294d4afb5ceSopenharmony_ci	case LWS_CALLBACK_RAW_CLOSE_FILE:
295d4afb5ceSopenharmony_ci		lwsl_info("%s: closed\n", __func__);
296d4afb5ceSopenharmony_ci		break;
297d4afb5ceSopenharmony_ci
298d4afb5ceSopenharmony_ci	case LWS_CALLBACK_RAW_RX_FILE:
299d4afb5ceSopenharmony_ci		/* we come here about every 250ms */
300d4afb5ceSopenharmony_ci
301d4afb5ceSopenharmony_ci		/*
302d4afb5ceSopenharmony_ci		 * Playing back the mp3?
303d4afb5ceSopenharmony_ci		 */
304d4afb5ceSopenharmony_ci		if (vhd->mode == MODE_PLAYING && vhd->mh) {
305d4afb5ceSopenharmony_ci			size_t amt, try;
306d4afb5ceSopenharmony_ci
307d4afb5ceSopenharmony_ci			do {
308d4afb5ceSopenharmony_ci				try = snd_pcm_avail(vhd->pcm_playback);
309d4afb5ceSopenharmony_ci				if (try > LWS_ARRAY_SIZE(vhd->p))
310d4afb5ceSopenharmony_ci					try = LWS_ARRAY_SIZE(vhd->p);
311d4afb5ceSopenharmony_ci
312d4afb5ceSopenharmony_ci				n = mpg123_read(vhd->mh, (uint8_t *)vhd->p,
313d4afb5ceSopenharmony_ci						try * 2, &amt);
314d4afb5ceSopenharmony_ci				lwsl_info("%s: PLAYING: mpg123 read %d, n %d\n",
315d4afb5ceSopenharmony_ci						__func__, (int)amt, n);
316d4afb5ceSopenharmony_ci				if (n == MPG123_NEW_FORMAT) {
317d4afb5ceSopenharmony_ci					snd_pcm_start(vhd->pcm_playback);
318d4afb5ceSopenharmony_ci					memset(vhd->p, 0, try);
319d4afb5ceSopenharmony_ci					snd_pcm_writei(vhd->pcm_playback,
320d4afb5ceSopenharmony_ci						       vhd->p, try / 2);
321d4afb5ceSopenharmony_ci					snd_pcm_prepare(vhd->pcm_playback);
322d4afb5ceSopenharmony_ci				}
323d4afb5ceSopenharmony_ci			} while (n == MPG123_NEW_FORMAT);
324d4afb5ceSopenharmony_ci
325d4afb5ceSopenharmony_ci			if (amt) {
326d4afb5ceSopenharmony_ci				n = snd_pcm_writei(vhd->pcm_playback,
327d4afb5ceSopenharmony_ci						   vhd->p, amt / 2);
328d4afb5ceSopenharmony_ci				if (n < 0)
329d4afb5ceSopenharmony_ci					lwsl_notice("%s: snd_pcm_writei: %d %s\n",
330d4afb5ceSopenharmony_ci						    __func__, n, snd_strerror(n));
331d4afb5ceSopenharmony_ci				if (n == -EPIPE) {
332d4afb5ceSopenharmony_ci					lwsl_err("%s: did EPIPE prep\n", __func__);
333d4afb5ceSopenharmony_ci					snd_pcm_prepare(vhd->pcm_playback);
334d4afb5ceSopenharmony_ci				}
335d4afb5ceSopenharmony_ci			} else
336d4afb5ceSopenharmony_ci				if (vhd->destroy_mh_on_drain &&
337d4afb5ceSopenharmony_ci				    n != MPG123_NEW_FORMAT) {
338d4afb5ceSopenharmony_ci					snd_pcm_drain(vhd->pcm_playback);
339d4afb5ceSopenharmony_ci					vhd->destroy_mh_on_drain = 0;
340d4afb5ceSopenharmony_ci					lwsl_notice("%s: mp3 destroyed\n",
341d4afb5ceSopenharmony_ci							__func__);
342d4afb5ceSopenharmony_ci					mpg123_close(vhd->mh);
343d4afb5ceSopenharmony_ci					mpg123_delete(vhd->mh);
344d4afb5ceSopenharmony_ci					vhd->mh = NULL;
345d4afb5ceSopenharmony_ci					vhd->mode = MODE_IDLE;
346d4afb5ceSopenharmony_ci
347d4afb5ceSopenharmony_ci					if (vhd->done_cb)
348d4afb5ceSopenharmony_ci						vhd->done_cb(vhd->opaque);
349d4afb5ceSopenharmony_ci				}
350d4afb5ceSopenharmony_ci		}
351d4afb5ceSopenharmony_ci
352d4afb5ceSopenharmony_ci		/*
353d4afb5ceSopenharmony_ci		 * Get the capture data
354d4afb5ceSopenharmony_ci		 */
355d4afb5ceSopenharmony_ci
356d4afb5ceSopenharmony_ci		n = snd_pcm_readi(vhd->pcm_capture, temp, LWS_ARRAY_SIZE(temp));
357d4afb5ceSopenharmony_ci		s = 0;
358d4afb5ceSopenharmony_ci		while (s < n) {
359d4afb5ceSopenharmony_ci			vhd->p[(vhd->wpos + s) % LWS_ARRAY_SIZE(vhd->p)] = temp[s];
360d4afb5ceSopenharmony_ci			s++;
361d4afb5ceSopenharmony_ci		}
362d4afb5ceSopenharmony_ci
363d4afb5ceSopenharmony_ci		if (vhd->mode == MODE_CAPTURING) {
364d4afb5ceSopenharmony_ci
365d4afb5ceSopenharmony_ci			/*
366d4afb5ceSopenharmony_ci			 * We are recording an utterance.
367d4afb5ceSopenharmony_ci			 *
368d4afb5ceSopenharmony_ci			 * Estimate the sound density in the frame by picking 50
369d4afb5ceSopenharmony_ci			 * samples at random and averaging the sampled
370d4afb5ceSopenharmony_ci			 * [abs()^2] / 10000 to create a Figure of Merit.
371d4afb5ceSopenharmony_ci			 *
372d4afb5ceSopenharmony_ci			 * Speaking on my laptop gets us 1000 - 5000, silence
373d4afb5ceSopenharmony_ci			 * is typ under 30.  The wakeword tells us there was
374d4afb5ceSopenharmony_ci			 * speech at the start, end the capture when there's
375d4afb5ceSopenharmony_ci			 * ~750ms (12000 samples) under 125 FOM.
376d4afb5ceSopenharmony_ci			 */
377d4afb5ceSopenharmony_ci
378d4afb5ceSopenharmony_ci#define SILENCE_THRESH 125
379d4afb5ceSopenharmony_ci
380d4afb5ceSopenharmony_ci			avg = 0;
381d4afb5ceSopenharmony_ci			lws_get_random(lws_get_context(wsi), rands, sizeof(rands));
382d4afb5ceSopenharmony_ci			for (s = 0; s < (int)LWS_ARRAY_SIZE(rands); s++) {
383d4afb5ceSopenharmony_ci				long q;
384d4afb5ceSopenharmony_ci
385d4afb5ceSopenharmony_ci				q = temp[rands[s] % n];
386d4afb5ceSopenharmony_ci
387d4afb5ceSopenharmony_ci				avg += (q * q);
388d4afb5ceSopenharmony_ci			}
389d4afb5ceSopenharmony_ci			avg = (avg / (int)LWS_ARRAY_SIZE(rands)) / 10000;
390d4afb5ceSopenharmony_ci
391d4afb5ceSopenharmony_ci			lwsl_notice("est audio energy: %ld %d\n", avg, vhd->mode);
392d4afb5ceSopenharmony_ci
393d4afb5ceSopenharmony_ci			/*
394d4afb5ceSopenharmony_ci			 * Only start looking for "silence" after 1.5s, in case
395d4afb5ceSopenharmony_ci			 * he does a long pause after the wakeword
396d4afb5ceSopenharmony_ci			 */
397d4afb5ceSopenharmony_ci
398d4afb5ceSopenharmony_ci			if (vhd->anycount < (3 *vhd->rate) / 2 &&
399d4afb5ceSopenharmony_ci			    avg < SILENCE_THRESH) {
400d4afb5ceSopenharmony_ci				vhd->quietcount += n;
401d4afb5ceSopenharmony_ci				/* then 500ms of "silence" does it for us */
402d4afb5ceSopenharmony_ci				if (vhd->quietcount >= ((vhd->rate * 3) / 4)) {
403d4afb5ceSopenharmony_ci					lwsl_warn("%s: ended capture\n", __func__);
404d4afb5ceSopenharmony_ci					vhd->mode = MODE_IDLE;
405d4afb5ceSopenharmony_ci					vhd->quietcount = 0;
406d4afb5ceSopenharmony_ci				}
407d4afb5ceSopenharmony_ci			}
408d4afb5ceSopenharmony_ci
409d4afb5ceSopenharmony_ci			/* if we're not "silent", reset the count */
410d4afb5ceSopenharmony_ci			if (avg > SILENCE_THRESH * 2)
411d4afb5ceSopenharmony_ci				vhd->quietcount = 0;
412d4afb5ceSopenharmony_ci
413d4afb5ceSopenharmony_ci			/*
414d4afb5ceSopenharmony_ci			 * Since we are in capturing mode, we have something
415d4afb5ceSopenharmony_ci			 * new to send now.
416d4afb5ceSopenharmony_ci			 *
417d4afb5ceSopenharmony_ci			 * We must send an extra one at the end so we can finish
418d4afb5ceSopenharmony_ci			 * the tx.
419d4afb5ceSopenharmony_ci			 */
420d4afb5ceSopenharmony_ci			lws_ss_request_tx(hss_avs_sync);
421d4afb5ceSopenharmony_ci		}
422d4afb5ceSopenharmony_ci
423d4afb5ceSopenharmony_ci		/*
424d4afb5ceSopenharmony_ci		 * Just waiting for a wakeword
425d4afb5ceSopenharmony_ci		 */
426d4afb5ceSopenharmony_ci
427d4afb5ceSopenharmony_ci		while (vhd->mode == MODE_IDLE) {
428d4afb5ceSopenharmony_ci			int m = 0, ppold = vhd->porcpos;
429d4afb5ceSopenharmony_ci
430d4afb5ceSopenharmony_ci			s = (vhd->wpos - vhd->porcpos) % LWS_ARRAY_SIZE(vhd->p);
431d4afb5ceSopenharmony_ci			if (s < vhd->porc_spf)
432d4afb5ceSopenharmony_ci				goto eol;
433d4afb5ceSopenharmony_ci
434d4afb5ceSopenharmony_ci			while (m < vhd->porc_spf) {
435d4afb5ceSopenharmony_ci				vhd->porcbuf[m++] = avhd->p[vhd->porcpos];
436d4afb5ceSopenharmony_ci				vhd->porcpos = (vhd->porcpos + 1) %
437d4afb5ceSopenharmony_ci							LWS_ARRAY_SIZE(vhd->p);
438d4afb5ceSopenharmony_ci			}
439d4afb5ceSopenharmony_ci
440d4afb5ceSopenharmony_ci			if (pv_porcupine_process(vhd->porc, vhd->porcbuf, &det))
441d4afb5ceSopenharmony_ci				lwsl_err("%s: porc_process failed\n", __func__);
442d4afb5ceSopenharmony_ci
443d4afb5ceSopenharmony_ci			if (!det && vhd->last_wake_detect &&
444d4afb5ceSopenharmony_ci			    vhd->mode == MODE_IDLE) {
445d4afb5ceSopenharmony_ci				lwsl_warn("************* Wakeword\n");
446d4afb5ceSopenharmony_ci				if (!avs_query_start(lws_get_context(wsi))) {
447d4afb5ceSopenharmony_ci					vhd->mode = MODE_CAPTURING;
448d4afb5ceSopenharmony_ci					vhd->quietcount = 0;
449d4afb5ceSopenharmony_ci					vhd->last_wake_detect = det;
450d4afb5ceSopenharmony_ci					vhd->npos = ppold;
451d4afb5ceSopenharmony_ci					break;
452d4afb5ceSopenharmony_ci				}
453d4afb5ceSopenharmony_ci			}
454d4afb5ceSopenharmony_ci			vhd->last_wake_detect = det;
455d4afb5ceSopenharmony_ci		}
456d4afb5ceSopenharmony_ci
457d4afb5ceSopenharmony_cieol:
458d4afb5ceSopenharmony_ci		vhd->wpos = (vhd->wpos + n) % LWS_ARRAY_SIZE(vhd->p);
459d4afb5ceSopenharmony_ci		break;
460d4afb5ceSopenharmony_ci
461d4afb5ceSopenharmony_ci	default:
462d4afb5ceSopenharmony_ci		break;
463d4afb5ceSopenharmony_ci	}
464d4afb5ceSopenharmony_ci
465d4afb5ceSopenharmony_ci	return 0;
466d4afb5ceSopenharmony_ci}
467d4afb5ceSopenharmony_ci
468d4afb5ceSopenharmony_cistruct lws_protocols protocol_audio_test =
469d4afb5ceSopenharmony_ci	{ "lws-audio-test", callback_audio, 0, 0 };
470