1d4afb5ceSopenharmony_ci/* 2d4afb5ceSopenharmony_ci * lws-minimal-secure-streams-alexa 3d4afb5ceSopenharmony_ci * 4d4afb5ceSopenharmony_ci * This file is made available under the Creative Commons CC0 1.0 5d4afb5ceSopenharmony_ci * Universal Public Domain Dedication. 6d4afb5ceSopenharmony_ci */ 7d4afb5ceSopenharmony_ci 8d4afb5ceSopenharmony_ci#include <libwebsockets.h> 9d4afb5ceSopenharmony_ci#include <string.h> 10d4afb5ceSopenharmony_ci#include <sys/types.h> 11d4afb5ceSopenharmony_ci#include <sys/stat.h> 12d4afb5ceSopenharmony_ci#include <unistd.h> 13d4afb5ceSopenharmony_ci#include <fcntl.h> 14d4afb5ceSopenharmony_ci 15d4afb5ceSopenharmony_ci#include <mpg123.h> 16d4afb5ceSopenharmony_ci 17d4afb5ceSopenharmony_ci#include "private.h" 18d4afb5ceSopenharmony_ci 19d4afb5ceSopenharmony_cistruct lws_ss_handle *hss_avs_event, *hss_avs_sync; 20d4afb5ceSopenharmony_ci 21d4afb5ceSopenharmony_ci/* this is the type for the long poll event channel */ 22d4afb5ceSopenharmony_ci 23d4afb5ceSopenharmony_citypedef struct ss_avs_event { 24d4afb5ceSopenharmony_ci struct lws_ss_handle *ss; 25d4afb5ceSopenharmony_ci void *opaque_data; 26d4afb5ceSopenharmony_ci /* ... application specific state ... */ 27d4afb5ceSopenharmony_ci 28d4afb5ceSopenharmony_ci struct lejp_ctx jctx; 29d4afb5ceSopenharmony_ci} ss_avs_event_t; 30d4afb5ceSopenharmony_ci 31d4afb5ceSopenharmony_cienum { 32d4afb5ceSopenharmony_ci LAMP3STATE_IDLE, 33d4afb5ceSopenharmony_ci LAMP3STATE_SPOOLING, 34d4afb5ceSopenharmony_ci LAMP3STATE_DRAINING, 35d4afb5ceSopenharmony_ci}; 36d4afb5ceSopenharmony_ci 37d4afb5ceSopenharmony_ci/* this is the type for the utterance metadata (and audio rideshares) */ 38d4afb5ceSopenharmony_ci 39d4afb5ceSopenharmony_citypedef struct ss_avs_metadata { 40d4afb5ceSopenharmony_ci struct lws_ss_handle *ss; 41d4afb5ceSopenharmony_ci void *opaque_data; 42d4afb5ceSopenharmony_ci /* ... application specific state ... */ 43d4afb5ceSopenharmony_ci 44d4afb5ceSopenharmony_ci struct lws_buflist *dribble; /* next mp3 data while draining last */ 45d4afb5ceSopenharmony_ci 46d4afb5ceSopenharmony_ci struct lejp_ctx jctx; 47d4afb5ceSopenharmony_ci size_t pos; 48d4afb5ceSopenharmony_ci size_t mp3_in; 49d4afb5ceSopenharmony_ci mpg123_handle *mh; 50d4afb5ceSopenharmony_ci 51d4afb5ceSopenharmony_ci lws_sorted_usec_list_t sul; 52d4afb5ceSopenharmony_ci 53d4afb5ceSopenharmony_ci uint8_t stash_eom[16]; 54d4afb5ceSopenharmony_ci 55d4afb5ceSopenharmony_ci uint8_t se_head; 56d4afb5ceSopenharmony_ci uint8_t se_tail; 57d4afb5ceSopenharmony_ci 58d4afb5ceSopenharmony_ci char mp3_state; 59d4afb5ceSopenharmony_ci char first_mp3; 60d4afb5ceSopenharmony_ci uint8_t mp3_mime_match; 61d4afb5ceSopenharmony_ci uint8_t seen; 62d4afb5ceSopenharmony_ci uint8_t inside_mp3; 63d4afb5ceSopenharmony_ci 64d4afb5ceSopenharmony_ci} ss_avs_metadata_t; 65d4afb5ceSopenharmony_ci 66d4afb5ceSopenharmony_ci/* 67d4afb5ceSopenharmony_ci * The remote server only seems to give us a budget of 10s to consume the 68d4afb5ceSopenharmony_ci * results, after that it doesn't drop the stream, but doesn't send us anything 69d4afb5ceSopenharmony_ci * further on it. 70d4afb5ceSopenharmony_ci * 71d4afb5ceSopenharmony_ci * This makes it impossible to optimize buffering for incoming mp3 since we 72d4afb5ceSopenharmony_ci * have to go ahead and take it before the 10s is up. 73d4afb5ceSopenharmony_ci */ 74d4afb5ceSopenharmony_ci 75d4afb5ceSopenharmony_ci#define MAX_MP3_IN_BUFFERING_BYTES 32768 76d4afb5ceSopenharmony_ci 77d4afb5ceSopenharmony_ci/* 78d4afb5ceSopenharmony_ci * Structure of JSON metadata for utterance handling 79d4afb5ceSopenharmony_ci */ 80d4afb5ceSopenharmony_ci 81d4afb5ceSopenharmony_cistatic const char *metadata = "{" 82d4afb5ceSopenharmony_ci "\"event\": {" 83d4afb5ceSopenharmony_ci "\"header\": {" 84d4afb5ceSopenharmony_ci "\"namespace\": \"SpeechRecognizer\"," 85d4afb5ceSopenharmony_ci "\"name\": \"Recognize\"," 86d4afb5ceSopenharmony_ci "\"messageId\": \"message-123\"," 87d4afb5ceSopenharmony_ci "\"dialogRequestId\": \"dialog-request-321\"" 88d4afb5ceSopenharmony_ci "}," 89d4afb5ceSopenharmony_ci "\"payload\": {" 90d4afb5ceSopenharmony_ci "\"profile\":" "\"CLOSE_TALK\"," 91d4afb5ceSopenharmony_ci "\"format\":" "\"AUDIO_L16_RATE_16000_CHANNELS_1\"" 92d4afb5ceSopenharmony_ci "}" 93d4afb5ceSopenharmony_ci "}" 94d4afb5ceSopenharmony_ci"}"; 95d4afb5ceSopenharmony_ci 96d4afb5ceSopenharmony_ci/* 97d4afb5ceSopenharmony_ci * avs metadata 98d4afb5ceSopenharmony_ci */ 99d4afb5ceSopenharmony_ci 100d4afb5ceSopenharmony_cistatic void 101d4afb5ceSopenharmony_ciuse_buffer_250ms(lws_sorted_usec_list_t *sul) 102d4afb5ceSopenharmony_ci{ 103d4afb5ceSopenharmony_ci ss_avs_metadata_t *m = lws_container_of(sul, ss_avs_metadata_t, sul); 104d4afb5ceSopenharmony_ci struct lws_context *context = (struct lws_context *)m->opaque_data; 105d4afb5ceSopenharmony_ci int est = lws_ss_get_est_peer_tx_credit(m->ss); 106d4afb5ceSopenharmony_ci 107d4afb5ceSopenharmony_ci lwsl_notice("%s: est txcr %d\n", __func__, est); 108d4afb5ceSopenharmony_ci 109d4afb5ceSopenharmony_ci if (est < MAX_MP3_IN_BUFFERING_BYTES - (MAX_MP3_IN_BUFFERING_BYTES / 4)) { 110d4afb5ceSopenharmony_ci lwsl_notice(" adding %d\n", MAX_MP3_IN_BUFFERING_BYTES / 4); 111d4afb5ceSopenharmony_ci lws_ss_add_peer_tx_credit(m->ss, MAX_MP3_IN_BUFFERING_BYTES / 4); 112d4afb5ceSopenharmony_ci } 113d4afb5ceSopenharmony_ci 114d4afb5ceSopenharmony_ci lws_sul_schedule(context, 0, &m->sul, use_buffer_250ms, 115d4afb5ceSopenharmony_ci 250 * LWS_US_PER_MS); 116d4afb5ceSopenharmony_ci} 117d4afb5ceSopenharmony_ci 118d4afb5ceSopenharmony_cistatic const char *mp3_mimetype = "application/octet-stream", 119d4afb5ceSopenharmony_ci *match2 = "\x0d\x0a\x0d\x0a"; 120d4afb5ceSopenharmony_ci 121d4afb5ceSopenharmony_cistatic int 122d4afb5ceSopenharmony_ciss_avs_mp3_open(ss_avs_metadata_t *m) 123d4afb5ceSopenharmony_ci{ 124d4afb5ceSopenharmony_ci int r; 125d4afb5ceSopenharmony_ci 126d4afb5ceSopenharmony_ci lwsl_notice("%s\n", __func__); 127d4afb5ceSopenharmony_ci 128d4afb5ceSopenharmony_ci m->first_mp3 = 1; 129d4afb5ceSopenharmony_ci m->mh = mpg123_new(NULL, NULL); 130d4afb5ceSopenharmony_ci if (!m->mh) { 131d4afb5ceSopenharmony_ci lwsl_err("%s: unable to make new mp3\n", 132d4afb5ceSopenharmony_ci __func__); 133d4afb5ceSopenharmony_ci goto bail; 134d4afb5ceSopenharmony_ci } 135d4afb5ceSopenharmony_ci mpg123_format_none(m->mh); 136d4afb5ceSopenharmony_ci r = mpg123_format(m->mh, 16000, MPG123_M_MONO, 137d4afb5ceSopenharmony_ci MPG123_ENC_SIGNED_16); 138d4afb5ceSopenharmony_ci if (r) { 139d4afb5ceSopenharmony_ci lwsl_err("%s: mpg123 format failed %d\n", 140d4afb5ceSopenharmony_ci __func__, r); 141d4afb5ceSopenharmony_ci goto bail1; 142d4afb5ceSopenharmony_ci } 143d4afb5ceSopenharmony_ci r = mpg123_open_feed(m->mh); 144d4afb5ceSopenharmony_ci if (r) { 145d4afb5ceSopenharmony_ci lwsl_err("%s: mpg123 open feed failed %d\n", 146d4afb5ceSopenharmony_ci __func__, r); 147d4afb5ceSopenharmony_ci goto bail1; 148d4afb5ceSopenharmony_ci } 149d4afb5ceSopenharmony_ci 150d4afb5ceSopenharmony_ci return 0; 151d4afb5ceSopenharmony_ci 152d4afb5ceSopenharmony_cibail1: 153d4afb5ceSopenharmony_ci mpg123_delete(m->mh); 154d4afb5ceSopenharmony_ci m->mh = NULL; 155d4afb5ceSopenharmony_ci 156d4afb5ceSopenharmony_cibail: 157d4afb5ceSopenharmony_ci return 1; 158d4afb5ceSopenharmony_ci} 159d4afb5ceSopenharmony_ci 160d4afb5ceSopenharmony_cistatic lws_ss_state_return_t 161d4afb5ceSopenharmony_ciss_avs_metadata_rx(void *userobj, const uint8_t *buf, size_t len, int flags); 162d4afb5ceSopenharmony_ci 163d4afb5ceSopenharmony_ci/* 164d4afb5ceSopenharmony_ci * This is called when the mp3 has drained it's input buffer and destroyed 165d4afb5ceSopenharmony_ci * itself. 166d4afb5ceSopenharmony_ci */ 167d4afb5ceSopenharmony_ci 168d4afb5ceSopenharmony_cistatic int 169d4afb5ceSopenharmony_cidrain_end_cb(void *v) 170d4afb5ceSopenharmony_ci{ 171d4afb5ceSopenharmony_ci ss_avs_metadata_t *m = (ss_avs_metadata_t *)v; 172d4afb5ceSopenharmony_ci struct lws_context *context = (struct lws_context *)m->opaque_data; 173d4afb5ceSopenharmony_ci int tot = 0; 174d4afb5ceSopenharmony_ci 175d4afb5ceSopenharmony_ci lwsl_err("%s\n", __func__); 176d4afb5ceSopenharmony_ci 177d4afb5ceSopenharmony_ci /* 178d4afb5ceSopenharmony_ci * We have drained and destroyed the existing mp3 session. Is there 179d4afb5ceSopenharmony_ci * a new one pending? 180d4afb5ceSopenharmony_ci */ 181d4afb5ceSopenharmony_ci 182d4afb5ceSopenharmony_ci m->first_mp3 = 1; 183d4afb5ceSopenharmony_ci m->mp3_state = LAMP3STATE_IDLE; 184d4afb5ceSopenharmony_ci 185d4afb5ceSopenharmony_ci if (lws_buflist_total_len(&m->dribble)) { 186d4afb5ceSopenharmony_ci /* we started another one */ 187d4afb5ceSopenharmony_ci 188d4afb5ceSopenharmony_ci /* resume tx credit top up */ 189d4afb5ceSopenharmony_ci lws_sul_schedule(context, 0, &m->sul, use_buffer_250ms, 1); 190d4afb5ceSopenharmony_ci 191d4afb5ceSopenharmony_ci if (ss_avs_mp3_open(m)) 192d4afb5ceSopenharmony_ci return 1; 193d4afb5ceSopenharmony_ci 194d4afb5ceSopenharmony_ci m->mp3_state = LAMP3STATE_SPOOLING; 195d4afb5ceSopenharmony_ci 196d4afb5ceSopenharmony_ci /* 197d4afb5ceSopenharmony_ci * Dump what we stashed from draining into the new mp3 198d4afb5ceSopenharmony_ci */ 199d4afb5ceSopenharmony_ci 200d4afb5ceSopenharmony_ci while (lws_buflist_total_len(&m->dribble)) { 201d4afb5ceSopenharmony_ci size_t s; 202d4afb5ceSopenharmony_ci uint8_t *u, t; 203d4afb5ceSopenharmony_ci 204d4afb5ceSopenharmony_ci s = lws_buflist_next_segment_len(&m->dribble, &u); 205d4afb5ceSopenharmony_ci t = m->stash_eom[m->se_tail]; 206d4afb5ceSopenharmony_ci lwsl_notice("%s: preload %d: %d\n", __func__, (int)s, t); 207d4afb5ceSopenharmony_ci 208d4afb5ceSopenharmony_ci mpg123_feed(m->mh, u, s); 209d4afb5ceSopenharmony_ci lws_buflist_use_segment(&m->dribble, s); 210d4afb5ceSopenharmony_ci if (m->first_mp3) { 211d4afb5ceSopenharmony_ci play_mp3(m->mh, NULL, NULL); 212d4afb5ceSopenharmony_ci m->first_mp3 = 0; 213d4afb5ceSopenharmony_ci } 214d4afb5ceSopenharmony_ci 215d4afb5ceSopenharmony_ci tot += s; 216d4afb5ceSopenharmony_ci 217d4afb5ceSopenharmony_ci m->se_tail = (m->se_tail + 1) % sizeof(m->stash_eom); 218d4afb5ceSopenharmony_ci if (t) { 219d4afb5ceSopenharmony_ci lwsl_notice("%s: preloaded EOM\n", __func__); 220d4afb5ceSopenharmony_ci 221d4afb5ceSopenharmony_ci /* 222d4afb5ceSopenharmony_ci * We stashed the whole of the message, we need 223d4afb5ceSopenharmony_ci * to also do the EOM processing. We will come 224d4afb5ceSopenharmony_ci * back here if there's another message in the 225d4afb5ceSopenharmony_ci * stash. 226d4afb5ceSopenharmony_ci */ 227d4afb5ceSopenharmony_ci 228d4afb5ceSopenharmony_ci m->mp3_state = LAMP3STATE_DRAINING; 229d4afb5ceSopenharmony_ci if (m->mh) 230d4afb5ceSopenharmony_ci play_mp3(NULL, drain_end_cb, m); 231d4afb5ceSopenharmony_ci 232d4afb5ceSopenharmony_ci lws_ss_add_peer_tx_credit(m->ss, tot); 233d4afb5ceSopenharmony_ci#if 0 234d4afb5ceSopenharmony_ci /* 235d4afb5ceSopenharmony_ci * Put a hold on bringing in any more data 236d4afb5ceSopenharmony_ci */ 237d4afb5ceSopenharmony_ci lws_sul_cancel(&m->sul); 238d4afb5ceSopenharmony_ci#endif 239d4afb5ceSopenharmony_ci /* destroy our copy of the handle */ 240d4afb5ceSopenharmony_ci m->mh = NULL; 241d4afb5ceSopenharmony_ci 242d4afb5ceSopenharmony_ci break; 243d4afb5ceSopenharmony_ci } 244d4afb5ceSopenharmony_ci } 245d4afb5ceSopenharmony_ci 246d4afb5ceSopenharmony_ci lws_ss_add_peer_tx_credit(m->ss, tot); 247d4afb5ceSopenharmony_ci } 248d4afb5ceSopenharmony_ci 249d4afb5ceSopenharmony_ci return 0; 250d4afb5ceSopenharmony_ci} 251d4afb5ceSopenharmony_ci 252d4afb5ceSopenharmony_cistatic lws_ss_state_return_t 253d4afb5ceSopenharmony_ciss_avs_metadata_rx(void *userobj, const uint8_t *buf, size_t len, int flags) 254d4afb5ceSopenharmony_ci{ 255d4afb5ceSopenharmony_ci ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj; 256d4afb5ceSopenharmony_ci struct lws_context *context = (struct lws_context *)m->opaque_data; 257d4afb5ceSopenharmony_ci int n = 0, hit = 0; 258d4afb5ceSopenharmony_ci 259d4afb5ceSopenharmony_ci lwsl_notice("%s: len %d, flags %d (est peer txcr %d)\n", __func__, 260d4afb5ceSopenharmony_ci (int)len, flags, lws_ss_get_est_peer_tx_credit(m->ss)); 261d4afb5ceSopenharmony_ci 262d4afb5ceSopenharmony_ci // lwsl_hexdump_warn(buf, len); 263d4afb5ceSopenharmony_ci 264d4afb5ceSopenharmony_ci if ((flags & LWSSS_FLAG_SOM) && !m->mh && !m->seen) { 265d4afb5ceSopenharmony_ci m->mp3_mime_match = 0; 266d4afb5ceSopenharmony_ci m->seen = 0; 267d4afb5ceSopenharmony_ci m->inside_mp3 = 0; 268d4afb5ceSopenharmony_ci } 269d4afb5ceSopenharmony_ci 270d4afb5ceSopenharmony_ci if (!m->inside_mp3) { 271d4afb5ceSopenharmony_ci /* 272d4afb5ceSopenharmony_ci * Identify the part with the mp3 in, if any 273d4afb5ceSopenharmony_ci */ 274d4afb5ceSopenharmony_ci 275d4afb5ceSopenharmony_ci while (n < (int)len - 24) { 276d4afb5ceSopenharmony_ci if (!m->seen) { 277d4afb5ceSopenharmony_ci if (buf[n] == mp3_mimetype[m->mp3_mime_match]) { 278d4afb5ceSopenharmony_ci m->mp3_mime_match++; 279d4afb5ceSopenharmony_ci if (m->mp3_mime_match == 24) { 280d4afb5ceSopenharmony_ci m->mp3_mime_match = 0; 281d4afb5ceSopenharmony_ci m->seen = 1; 282d4afb5ceSopenharmony_ci n++; 283d4afb5ceSopenharmony_ci continue; 284d4afb5ceSopenharmony_ci } 285d4afb5ceSopenharmony_ci } else 286d4afb5ceSopenharmony_ci m->mp3_mime_match = 0; 287d4afb5ceSopenharmony_ci } else { 288d4afb5ceSopenharmony_ci if (buf[n] == match2[m->mp3_mime_match]) { 289d4afb5ceSopenharmony_ci m->mp3_mime_match++; 290d4afb5ceSopenharmony_ci if (m->mp3_mime_match == 4) { 291d4afb5ceSopenharmony_ci m->seen = 0; 292d4afb5ceSopenharmony_ci m->mp3_mime_match = 0; 293d4afb5ceSopenharmony_ci hit = 1; 294d4afb5ceSopenharmony_ci n++; 295d4afb5ceSopenharmony_ci buf += n; 296d4afb5ceSopenharmony_ci len -= n; 297d4afb5ceSopenharmony_ci lwsl_notice("identified reply...\n"); 298d4afb5ceSopenharmony_ci m->inside_mp3 = 1; 299d4afb5ceSopenharmony_ci break; 300d4afb5ceSopenharmony_ci } 301d4afb5ceSopenharmony_ci } else 302d4afb5ceSopenharmony_ci m->mp3_mime_match = 0; 303d4afb5ceSopenharmony_ci } 304d4afb5ceSopenharmony_ci 305d4afb5ceSopenharmony_ci n++; 306d4afb5ceSopenharmony_ci } 307d4afb5ceSopenharmony_ci 308d4afb5ceSopenharmony_ci if (!hit) { 309d4afb5ceSopenharmony_ci lws_ss_add_peer_tx_credit(m->ss, len); 310d4afb5ceSopenharmony_ci return 0; 311d4afb5ceSopenharmony_ci } 312d4afb5ceSopenharmony_ci } 313d4afb5ceSopenharmony_ci 314d4afb5ceSopenharmony_ci // lwsl_notice("%s: state %d\n", __func__, m->mp3_state); 315d4afb5ceSopenharmony_ci 316d4afb5ceSopenharmony_ci switch (m->mp3_state) { 317d4afb5ceSopenharmony_ci case LAMP3STATE_IDLE: 318d4afb5ceSopenharmony_ci 319d4afb5ceSopenharmony_ci if (hit) { 320d4afb5ceSopenharmony_ci 321d4afb5ceSopenharmony_ci lws_ss_add_peer_tx_credit(m->ss, n); 322d4afb5ceSopenharmony_ci 323d4afb5ceSopenharmony_ci if (ss_avs_mp3_open(m)) 324d4afb5ceSopenharmony_ci goto bail; 325d4afb5ceSopenharmony_ci 326d4afb5ceSopenharmony_ci lws_sul_schedule(context, 0, &m->sul, use_buffer_250ms, 1); 327d4afb5ceSopenharmony_ci m->mp3_state = LAMP3STATE_SPOOLING; 328d4afb5ceSopenharmony_ci break; 329d4afb5ceSopenharmony_ci } 330d4afb5ceSopenharmony_ci 331d4afb5ceSopenharmony_ci lws_ss_add_peer_tx_credit(m->ss, len); 332d4afb5ceSopenharmony_ci 333d4afb5ceSopenharmony_ci if (!m->inside_mp3) 334d4afb5ceSopenharmony_ci break; 335d4afb5ceSopenharmony_ci 336d4afb5ceSopenharmony_ci /* fallthru */ 337d4afb5ceSopenharmony_ci 338d4afb5ceSopenharmony_ci case LAMP3STATE_SPOOLING: 339d4afb5ceSopenharmony_ci 340d4afb5ceSopenharmony_ci if (m->dribble) 341d4afb5ceSopenharmony_ci goto draining; 342d4afb5ceSopenharmony_ci 343d4afb5ceSopenharmony_ci if (len) { 344d4afb5ceSopenharmony_ci /* 345d4afb5ceSopenharmony_ci * We are shoving encoded mp3 into mpg123-allocated heap 346d4afb5ceSopenharmony_ci * buffers... unfortunately mpg123 doesn't seem to 347d4afb5ceSopenharmony_ci * expose where it is in its allocated input so we can 348d4afb5ceSopenharmony_ci * track how much is stashed. Instead while in playback 349d4afb5ceSopenharmony_ci * mode, we assume 64kbps mp3 encoding, ie, 8KB/s, and 350d4afb5ceSopenharmony_ci * run a sul that allows an additional 2KB tx credit 351d4afb5ceSopenharmony_ci * every 250ms, with 4KB initial credit. 352d4afb5ceSopenharmony_ci */ 353d4afb5ceSopenharmony_ci lwsl_notice("%s: SPOOL %d\n", __func__, (int)len); 354d4afb5ceSopenharmony_ci mpg123_feed(m->mh, buf, len); 355d4afb5ceSopenharmony_ci 356d4afb5ceSopenharmony_ci if (m->first_mp3) { 357d4afb5ceSopenharmony_ci lws_sul_schedule(context, 0, &m->sul, 358d4afb5ceSopenharmony_ci use_buffer_250ms, 1); 359d4afb5ceSopenharmony_ci // lws_ss_add_peer_tx_credit(m->ss, 360d4afb5ceSopenharmony_ci // len + (MAX_MP3_IN_BUFFERING_BYTES / 2)); 361d4afb5ceSopenharmony_ci play_mp3(m->mh, NULL, NULL); 362d4afb5ceSopenharmony_ci } //else 363d4afb5ceSopenharmony_ci // lws_ss_add_peer_tx_credit(m->ss, len); 364d4afb5ceSopenharmony_ci m->first_mp3 = 0; 365d4afb5ceSopenharmony_ci } 366d4afb5ceSopenharmony_ci 367d4afb5ceSopenharmony_ci if (flags & LWSSS_FLAG_EOM) { 368d4afb5ceSopenharmony_ci /* 369d4afb5ceSopenharmony_ci * This means one "message" / mime part with mp3 data 370d4afb5ceSopenharmony_ci * has finished coming in. But there may be whole other 371d4afb5ceSopenharmony_ci * parts with other mp3s following, with potentially 372d4afb5ceSopenharmony_ci * different mp3 parameters. So we want to tell this 373d4afb5ceSopenharmony_ci * one to drain and finish and destroy the current mp3 374d4afb5ceSopenharmony_ci * object before we go on. 375d4afb5ceSopenharmony_ci * 376d4afb5ceSopenharmony_ci * But not knowing the length of the current one, there 377d4afb5ceSopenharmony_ci * will already be outstanding tx credit at the server, 378d4afb5ceSopenharmony_ci * so it's going to spam us with the next part before we 379d4afb5ceSopenharmony_ci * have the new mp3 sink for it. 380d4afb5ceSopenharmony_ci */ 381d4afb5ceSopenharmony_ci lwsl_notice("%s: EOM\n", __func__); 382d4afb5ceSopenharmony_ci m->mp3_mime_match = 0; 383d4afb5ceSopenharmony_ci m->seen = 0; 384d4afb5ceSopenharmony_ci m->mp3_state = LAMP3STATE_DRAINING; 385d4afb5ceSopenharmony_ci /* from input POV, we're no longer inside an mp3 */ 386d4afb5ceSopenharmony_ci m->inside_mp3 = 0; 387d4afb5ceSopenharmony_ci if (m->mh) 388d4afb5ceSopenharmony_ci play_mp3(NULL, drain_end_cb, m); 389d4afb5ceSopenharmony_ci#if 0 390d4afb5ceSopenharmony_ci /* 391d4afb5ceSopenharmony_ci * Put a hold on bringing in any more data 392d4afb5ceSopenharmony_ci */ 393d4afb5ceSopenharmony_ci lws_sul_cancel(&m->sul); 394d4afb5ceSopenharmony_ci#endif 395d4afb5ceSopenharmony_ci /* destroy our copy of the handle */ 396d4afb5ceSopenharmony_ci m->mh = NULL; 397d4afb5ceSopenharmony_ci } 398d4afb5ceSopenharmony_ci break; 399d4afb5ceSopenharmony_ci 400d4afb5ceSopenharmony_ci case LAMP3STATE_DRAINING: 401d4afb5ceSopenharmony_ci 402d4afb5ceSopenharmony_cidraining: 403d4afb5ceSopenharmony_ci if (buf && len && m->inside_mp3) { 404d4afb5ceSopenharmony_ci lwsl_notice("%s: DRAINING: stashing %d: %d %d %d\n", 405d4afb5ceSopenharmony_ci __func__, (int)len, !!(flags & LWSSS_FLAG_EOM), 406d4afb5ceSopenharmony_ci m->se_head, m->se_tail); 407d4afb5ceSopenharmony_ci lwsl_hexdump_notice(buf, len); 408d4afb5ceSopenharmony_ci if (lws_buflist_append_segment(&m->dribble, buf, len) < 0) 409d4afb5ceSopenharmony_ci goto bail; 410d4afb5ceSopenharmony_ci 411d4afb5ceSopenharmony_ci m->stash_eom[m->se_head] = !!(flags & LWSSS_FLAG_EOM); 412d4afb5ceSopenharmony_ci m->se_head = (m->se_head + 1) % sizeof(m->stash_eom); 413d4afb5ceSopenharmony_ci lwsl_notice("%s: next head %d\n", __func__, m->se_head); 414d4afb5ceSopenharmony_ci 415d4afb5ceSopenharmony_ci lws_ss_add_peer_tx_credit(m->ss, len); 416d4afb5ceSopenharmony_ci } 417d4afb5ceSopenharmony_ci 418d4afb5ceSopenharmony_ci if (flags & LWSSS_FLAG_EOM) { 419d4afb5ceSopenharmony_ci if (!len && m->se_head != m->se_tail) { 420d4afb5ceSopenharmony_ci /* 0-len EOM... retrospectively mark last stash */ 421d4afb5ceSopenharmony_ci lwsl_notice("%s: retro EOM\n", __func__); 422d4afb5ceSopenharmony_ci m->stash_eom[(m->se_head - 1) % sizeof(m->stash_eom)] = 1; 423d4afb5ceSopenharmony_ci } 424d4afb5ceSopenharmony_ci 425d4afb5ceSopenharmony_ci lwsl_notice("%s: Draining EOM\n", __func__); 426d4afb5ceSopenharmony_ci m->inside_mp3 = 0; 427d4afb5ceSopenharmony_ci } 428d4afb5ceSopenharmony_ci /* 429d4afb5ceSopenharmony_ci * Don't provide any additional tx credit... we're just 430d4afb5ceSopenharmony_ci * mopping up the overspill from the previous mp3 credit 431d4afb5ceSopenharmony_ci */ 432d4afb5ceSopenharmony_ci break; 433d4afb5ceSopenharmony_ci } 434d4afb5ceSopenharmony_ci 435d4afb5ceSopenharmony_ci return 0; 436d4afb5ceSopenharmony_ci 437d4afb5ceSopenharmony_cibail: 438d4afb5ceSopenharmony_ci return -1; 439d4afb5ceSopenharmony_ci} 440d4afb5ceSopenharmony_ci 441d4afb5ceSopenharmony_ci/* 442d4afb5ceSopenharmony_ci * Because this is multipart mime in h2 currently, use a "rideshare" to handle 443d4afb5ceSopenharmony_ci * first the native metadata on this secure stream, then the "rideshare" audio 444d4afb5ceSopenharmony_ci * stream mentioned in the policy. 445d4afb5ceSopenharmony_ci * 446d4afb5ceSopenharmony_ci * Lws takes care of interleaving the multipart mime pieces since the policy 447d4afb5ceSopenharmony_ci * calls for it. 448d4afb5ceSopenharmony_ci */ 449d4afb5ceSopenharmony_ci 450d4afb5ceSopenharmony_cistatic lws_ss_state_return_t 451d4afb5ceSopenharmony_ciss_avs_metadata_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, 452d4afb5ceSopenharmony_ci size_t *len, int *flags) 453d4afb5ceSopenharmony_ci{ 454d4afb5ceSopenharmony_ci ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj; 455d4afb5ceSopenharmony_ci size_t tot; 456d4afb5ceSopenharmony_ci int n; 457d4afb5ceSopenharmony_ci 458d4afb5ceSopenharmony_ci // lwsl_notice("%s %d\n", __func__, (int)m->pos); 459d4afb5ceSopenharmony_ci 460d4afb5ceSopenharmony_ci if ((long)m->pos < 0) { 461d4afb5ceSopenharmony_ci *len = 0; 462d4afb5ceSopenharmony_ci lwsl_info("%s: skip\n", __func__); 463d4afb5ceSopenharmony_ci return 1; 464d4afb5ceSopenharmony_ci } 465d4afb5ceSopenharmony_ci 466d4afb5ceSopenharmony_ci if (!strcmp(lws_ss_rideshare(m->ss), "avs_audio")) { 467d4afb5ceSopenharmony_ci 468d4afb5ceSopenharmony_ci /* audio rideshare part */ 469d4afb5ceSopenharmony_ci 470d4afb5ceSopenharmony_ci if (!m->pos) 471d4afb5ceSopenharmony_ci *flags |= LWSSS_FLAG_SOM; 472d4afb5ceSopenharmony_ci 473d4afb5ceSopenharmony_ci n = spool_capture(buf, *len); 474d4afb5ceSopenharmony_ci if (n > 0) 475d4afb5ceSopenharmony_ci *len = n; 476d4afb5ceSopenharmony_ci else 477d4afb5ceSopenharmony_ci *len = 0; 478d4afb5ceSopenharmony_ci if (!n) { 479d4afb5ceSopenharmony_ci lwsl_info("%s: trying to skip tx\n", __func__); 480d4afb5ceSopenharmony_ci return 1; 481d4afb5ceSopenharmony_ci } 482d4afb5ceSopenharmony_ci 483d4afb5ceSopenharmony_ci m->pos += *len; 484d4afb5ceSopenharmony_ci 485d4afb5ceSopenharmony_ci if (n < 0) { 486d4afb5ceSopenharmony_ci *flags |= LWSSS_FLAG_EOM; 487d4afb5ceSopenharmony_ci m->pos = (long)-1l; /* ban subsequent until new stream */ 488d4afb5ceSopenharmony_ci } 489d4afb5ceSopenharmony_ci 490d4afb5ceSopenharmony_ci lwsl_notice("%s: tx audio %d\n", __func__, (int)*len); 491d4afb5ceSopenharmony_ci 492d4afb5ceSopenharmony_ci#if 0 493d4afb5ceSopenharmony_ci { 494d4afb5ceSopenharmony_ci int ff = open("/tmp/z1", O_RDWR | O_CREAT | O_APPEND, 0666); 495d4afb5ceSopenharmony_ci if (ff == -1) 496d4afb5ceSopenharmony_ci lwsl_err("%s: errno %d\n", __func__, errno); 497d4afb5ceSopenharmony_ci write(ff, buf, *len); 498d4afb5ceSopenharmony_ci close(ff); 499d4afb5ceSopenharmony_ci } 500d4afb5ceSopenharmony_ci#endif 501d4afb5ceSopenharmony_ci 502d4afb5ceSopenharmony_ci return 0; 503d4afb5ceSopenharmony_ci } 504d4afb5ceSopenharmony_ci 505d4afb5ceSopenharmony_ci /* metadata part */ 506d4afb5ceSopenharmony_ci 507d4afb5ceSopenharmony_ci tot = strlen(metadata); 508d4afb5ceSopenharmony_ci 509d4afb5ceSopenharmony_ci if (!m->pos) 510d4afb5ceSopenharmony_ci *flags |= LWSSS_FLAG_SOM; 511d4afb5ceSopenharmony_ci 512d4afb5ceSopenharmony_ci if (*len > tot - m->pos) 513d4afb5ceSopenharmony_ci *len = tot - m->pos; 514d4afb5ceSopenharmony_ci 515d4afb5ceSopenharmony_ci memcpy(buf, metadata + m->pos, *len); 516d4afb5ceSopenharmony_ci 517d4afb5ceSopenharmony_ci m->pos += *len; 518d4afb5ceSopenharmony_ci 519d4afb5ceSopenharmony_ci if (m->pos == tot) { 520d4afb5ceSopenharmony_ci lwsl_notice("metadata done\n"); 521d4afb5ceSopenharmony_ci *flags |= LWSSS_FLAG_EOM; 522d4afb5ceSopenharmony_ci m->pos = 0; /* for next time */ 523d4afb5ceSopenharmony_ci } 524d4afb5ceSopenharmony_ci 525d4afb5ceSopenharmony_ci return 0; 526d4afb5ceSopenharmony_ci} 527d4afb5ceSopenharmony_ci 528d4afb5ceSopenharmony_cistatic lws_ss_state_return_t 529d4afb5ceSopenharmony_ciss_avs_metadata_state(void *userobj, void *sh, 530d4afb5ceSopenharmony_ci lws_ss_constate_t state, lws_ss_tx_ordinal_t ack) 531d4afb5ceSopenharmony_ci{ 532d4afb5ceSopenharmony_ci ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj; 533d4afb5ceSopenharmony_ci struct lws_context *context = (struct lws_context *)m->opaque_data; 534d4afb5ceSopenharmony_ci 535d4afb5ceSopenharmony_ci lwsl_notice("%s: %p: %s, ord 0x%x\n", __func__, m->ss, 536d4afb5ceSopenharmony_ci lws_ss_state_name(state), (unsigned int)ack); 537d4afb5ceSopenharmony_ci 538d4afb5ceSopenharmony_ci switch (state) { 539d4afb5ceSopenharmony_ci case LWSSSCS_CREATING: 540d4afb5ceSopenharmony_ci return lws_ss_client_connect(m->ss); 541d4afb5ceSopenharmony_ci 542d4afb5ceSopenharmony_ci case LWSSSCS_CONNECTING: 543d4afb5ceSopenharmony_ci m->pos = 0; 544d4afb5ceSopenharmony_ci break; 545d4afb5ceSopenharmony_ci case LWSSSCS_CONNECTED: 546d4afb5ceSopenharmony_ci lwsl_info("%s: CONNECTED\n", __func__); 547d4afb5ceSopenharmony_ci return lws_ss_request_tx(m->ss); 548d4afb5ceSopenharmony_ci 549d4afb5ceSopenharmony_ci case LWSSSCS_DISCONNECTED: 550d4afb5ceSopenharmony_ci lws_sul_cancel(&m->sul); 551d4afb5ceSopenharmony_ci //if (m->mh) { 552d4afb5ceSopenharmony_ci play_mp3(NULL, NULL, NULL); 553d4afb5ceSopenharmony_ci m->mh = NULL; 554d4afb5ceSopenharmony_ci //} 555d4afb5ceSopenharmony_ci /* 556d4afb5ceSopenharmony_ci * For this stream encapsulating an alexa exchange, dropping 557d4afb5ceSopenharmony_ci * is the end of its life 558d4afb5ceSopenharmony_ci */ 559d4afb5ceSopenharmony_ci return 1; 560d4afb5ceSopenharmony_ci 561d4afb5ceSopenharmony_ci case LWSSSCS_DESTROYING: 562d4afb5ceSopenharmony_ci lws_buflist_destroy_all_segments(&m->dribble); 563d4afb5ceSopenharmony_ci break; 564d4afb5ceSopenharmony_ci default: 565d4afb5ceSopenharmony_ci break; 566d4afb5ceSopenharmony_ci } 567d4afb5ceSopenharmony_ci 568d4afb5ceSopenharmony_ci return 0; 569d4afb5ceSopenharmony_ci} 570d4afb5ceSopenharmony_ci 571d4afb5ceSopenharmony_ci/* 572d4afb5ceSopenharmony_ci * avs event 573d4afb5ceSopenharmony_ci */ 574d4afb5ceSopenharmony_ci 575d4afb5ceSopenharmony_cistatic lws_ss_state_return_t 576d4afb5ceSopenharmony_ciss_avs_event_rx(void *userobj, const uint8_t *buf, size_t len, int flags) 577d4afb5ceSopenharmony_ci{ 578d4afb5ceSopenharmony_ci return 0; 579d4afb5ceSopenharmony_ci} 580d4afb5ceSopenharmony_ci 581d4afb5ceSopenharmony_cistatic lws_ss_state_return_t 582d4afb5ceSopenharmony_ciss_avs_event_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, 583d4afb5ceSopenharmony_ci size_t *len, int *flags) 584d4afb5ceSopenharmony_ci{ 585d4afb5ceSopenharmony_ci return 1; /* don't transmit anything */ 586d4afb5ceSopenharmony_ci} 587d4afb5ceSopenharmony_ci 588d4afb5ceSopenharmony_cistatic lws_ss_state_return_t 589d4afb5ceSopenharmony_ciss_avs_event_state(void *userobj, void *sh, 590d4afb5ceSopenharmony_ci lws_ss_constate_t state, lws_ss_tx_ordinal_t ack) 591d4afb5ceSopenharmony_ci{ 592d4afb5ceSopenharmony_ci lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), 593d4afb5ceSopenharmony_ci (unsigned int)ack); 594d4afb5ceSopenharmony_ci 595d4afb5ceSopenharmony_ci switch (state) { 596d4afb5ceSopenharmony_ci case LWSSSCS_CREATING: 597d4afb5ceSopenharmony_ci mpg123_init(); 598d4afb5ceSopenharmony_ci break; 599d4afb5ceSopenharmony_ci case LWSSSCS_CONNECTING: 600d4afb5ceSopenharmony_ci break; 601d4afb5ceSopenharmony_ci case LWSSSCS_CONNECTED: 602d4afb5ceSopenharmony_ci lwsl_user("Connected to Alexa... speak \"Alexa, ...\"\n"); 603d4afb5ceSopenharmony_ci break; 604d4afb5ceSopenharmony_ci case LWSSSCS_DISCONNECTED: 605d4afb5ceSopenharmony_ci lwsl_user("Disconnected from Alexa\n"); 606d4afb5ceSopenharmony_ci break; 607d4afb5ceSopenharmony_ci case LWSSSCS_DESTROYING: 608d4afb5ceSopenharmony_ci mpg123_exit(); 609d4afb5ceSopenharmony_ci break; 610d4afb5ceSopenharmony_ci default: 611d4afb5ceSopenharmony_ci break; 612d4afb5ceSopenharmony_ci } 613d4afb5ceSopenharmony_ci 614d4afb5ceSopenharmony_ci return 0; 615d4afb5ceSopenharmony_ci} 616d4afb5ceSopenharmony_ci 617d4afb5ceSopenharmony_ciint 618d4afb5ceSopenharmony_ciavs_query_start(struct lws_context *context) 619d4afb5ceSopenharmony_ci{ 620d4afb5ceSopenharmony_ci lws_ss_info_t ssi; 621d4afb5ceSopenharmony_ci 622d4afb5ceSopenharmony_ci lwsl_notice("%s:\n", __func__); 623d4afb5ceSopenharmony_ci 624d4afb5ceSopenharmony_ci memset(&ssi, 0, sizeof(ssi)); 625d4afb5ceSopenharmony_ci ssi.handle_offset = offsetof(ss_avs_metadata_t, ss); 626d4afb5ceSopenharmony_ci ssi.opaque_user_data_offset = offsetof(ss_avs_metadata_t, opaque_data); 627d4afb5ceSopenharmony_ci ssi.rx = ss_avs_metadata_rx; 628d4afb5ceSopenharmony_ci ssi.tx = ss_avs_metadata_tx; 629d4afb5ceSopenharmony_ci ssi.state = ss_avs_metadata_state; 630d4afb5ceSopenharmony_ci ssi.user_alloc = sizeof(ss_avs_metadata_t); 631d4afb5ceSopenharmony_ci ssi.streamtype = "avs_metadata"; 632d4afb5ceSopenharmony_ci 633d4afb5ceSopenharmony_ci ssi.manual_initial_tx_credit = 8192; 634d4afb5ceSopenharmony_ci 635d4afb5ceSopenharmony_ci if (lws_ss_create(context, 0, &ssi, context, &hss_avs_sync, NULL, NULL)) { 636d4afb5ceSopenharmony_ci lwsl_err("%s: failed to create avs metadata secstream\n", 637d4afb5ceSopenharmony_ci __func__); 638d4afb5ceSopenharmony_ci 639d4afb5ceSopenharmony_ci return 1; 640d4afb5ceSopenharmony_ci } 641d4afb5ceSopenharmony_ci 642d4afb5ceSopenharmony_ci lwsl_user("%s: created query stream %p\n", __func__, hss_avs_sync); 643d4afb5ceSopenharmony_ci 644d4afb5ceSopenharmony_ci return 0; 645d4afb5ceSopenharmony_ci} 646d4afb5ceSopenharmony_ci 647d4afb5ceSopenharmony_ciint 648d4afb5ceSopenharmony_ciavs_example_start(struct lws_context *context) 649d4afb5ceSopenharmony_ci{ 650d4afb5ceSopenharmony_ci lws_ss_info_t ssi; 651d4afb5ceSopenharmony_ci 652d4afb5ceSopenharmony_ci if (hss_avs_event) 653d4afb5ceSopenharmony_ci return 0; 654d4afb5ceSopenharmony_ci 655d4afb5ceSopenharmony_ci lwsl_info("%s: Starting AVS stream\n", __func__); 656d4afb5ceSopenharmony_ci 657d4afb5ceSopenharmony_ci /* AVS wants us to establish the long poll event stream first */ 658d4afb5ceSopenharmony_ci 659d4afb5ceSopenharmony_ci memset(&ssi, 0, sizeof(ssi)); 660d4afb5ceSopenharmony_ci ssi.handle_offset = offsetof(ss_avs_event_t, ss); 661d4afb5ceSopenharmony_ci ssi.opaque_user_data_offset = offsetof(ss_avs_event_t, opaque_data); 662d4afb5ceSopenharmony_ci ssi.rx = ss_avs_event_rx; 663d4afb5ceSopenharmony_ci ssi.tx = ss_avs_event_tx; 664d4afb5ceSopenharmony_ci ssi.state = ss_avs_event_state; 665d4afb5ceSopenharmony_ci ssi.user_alloc = sizeof(ss_avs_event_t); 666d4afb5ceSopenharmony_ci ssi.streamtype = "avs_event"; 667d4afb5ceSopenharmony_ci 668d4afb5ceSopenharmony_ci if (lws_ss_create(context, 0, &ssi, context, &hss_avs_event, NULL, NULL)) { 669d4afb5ceSopenharmony_ci lwsl_err("%s: failed to create avs event secure stream\n", 670d4afb5ceSopenharmony_ci __func__); 671d4afb5ceSopenharmony_ci return 1; 672d4afb5ceSopenharmony_ci } 673d4afb5ceSopenharmony_ci 674d4afb5ceSopenharmony_ci return 0; 675d4afb5ceSopenharmony_ci} 676