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