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(¶ms); 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