1/*
2 * lws-minimal-raw-audio
3 *
4 * Written in 2010-2019 by Andy Green <andy@warmcat.com>
5 *
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
8 *
9 * This demonstrates adopting and managing audio device file descriptors in the
10 * event loop.
11 */
12
13#include <libwebsockets.h>
14#include <string.h>
15#include <signal.h>
16#include <sys/types.h>
17#include <sys/stat.h>
18#include <fcntl.h>
19
20#include <alsa/asoundlib.h>
21
22static unsigned int sample_rate = 16000;
23
24struct raw_vhd {
25	uint8_t simplebuf[32768 * 2];
26	snd_pcm_t *pcm_capture;
27	snd_pcm_t *pcm_playback;
28	snd_pcm_hw_params_t *params;
29	snd_pcm_uframes_t frames;
30	int filefd;
31	int rpos;
32	int wpos;
33	int times;
34};
35
36static int
37set_hw_params(struct lws_vhost *vh, snd_pcm_t **pcm, int type)
38{
39	unsigned int rate = sample_rate;
40	snd_pcm_hw_params_t *params;
41	lws_sock_file_fd_type u;
42	struct pollfd pfd;
43	struct lws *wsi1;
44	int n;
45
46	n = snd_pcm_open(pcm, "default", type, SND_PCM_NONBLOCK);
47	if (n < 0) {
48		lwsl_err("%s: Can't open default for playback: %s\n",
49			 __func__, snd_strerror(n));
50
51		return -1;
52	}
53
54	if (snd_pcm_poll_descriptors(*pcm, &pfd, 1) != 1) {
55		lwsl_err("%s: failed to get playback desc\n", __func__);
56		return -1;
57	}
58
59	u.filefd = (lws_filefd_type)(long long)pfd.fd;
60	wsi1 = lws_adopt_descriptor_vhost(vh, LWS_ADOPT_RAW_FILE_DESC, u,
61					  "lws-audio-test", NULL);
62	if (!wsi1) {
63		lwsl_err("%s: Failed to adopt playback desc\n", __func__);
64		goto bail;
65	}
66	if (type == SND_PCM_STREAM_PLAYBACK)
67		lws_rx_flow_control(wsi1, 0); /* no POLLIN */
68
69	snd_pcm_hw_params_malloc(&params);
70	snd_pcm_hw_params_any(*pcm, params);
71
72	n = snd_pcm_hw_params_set_access(*pcm, params,
73					 SND_PCM_ACCESS_RW_INTERLEAVED);
74	if (n < 0)
75		goto bail1;
76
77	n = snd_pcm_hw_params_set_format(*pcm, params, SND_PCM_FORMAT_S16_LE);
78	if (n < 0)
79		goto bail1;
80
81	n = snd_pcm_hw_params_set_channels(*pcm, params, 1);
82	if (n < 0)
83		goto bail1;
84
85	n = snd_pcm_hw_params_set_rate_near(*pcm, params, &rate, 0);
86	if (n < 0)
87		goto bail1;
88
89	n = snd_pcm_hw_params(*pcm, params);
90	snd_pcm_hw_params_free(params);
91	if (n < 0)
92		goto bail;
93
94	return 0;
95
96bail1:
97	snd_pcm_hw_params_free(params);
98bail:
99	lwsl_err("%s: Set hw params failed: %s\n", __func__, snd_strerror(n));
100
101	return -1;
102}
103
104static int
105callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
106			void *user, void *in, size_t len)
107{
108	struct raw_vhd *vhd = (struct raw_vhd *)lws_protocol_vh_priv_get(
109				     lws_get_vhost(wsi), lws_get_protocol(wsi));
110	int n;
111
112	switch (reason) {
113	case LWS_CALLBACK_PROTOCOL_INIT:
114		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
115				lws_get_protocol(wsi), sizeof(struct raw_vhd));
116
117		if (set_hw_params(lws_get_vhost(wsi), &vhd->pcm_playback,
118				  SND_PCM_STREAM_PLAYBACK))  {
119			lwsl_err("%s: Can't open default for playback\n",
120				 __func__);
121
122			return -1;
123		}
124
125		if (set_hw_params(lws_get_vhost(wsi), &vhd->pcm_capture,
126				  SND_PCM_STREAM_CAPTURE))  {
127			lwsl_err("%s: Can't open default for capture\n",
128				 __func__);
129
130			return -1;
131		}
132		break;
133
134	case LWS_CALLBACK_PROTOCOL_DESTROY:
135		lwsl_notice("LWS_CALLBACK_PROTOCOL_DESTROY\n");
136		if (vhd && vhd->pcm_playback) {
137			snd_pcm_drain(vhd->pcm_playback);
138			snd_pcm_close(vhd->pcm_playback);
139			vhd->pcm_playback = NULL;
140		}
141		if (vhd && vhd->pcm_capture) {
142			snd_pcm_close(vhd->pcm_capture);
143			vhd->pcm_capture = NULL;
144		}
145		break;
146
147	case LWS_CALLBACK_RAW_RX_FILE:
148		if (vhd->times >= 6) {  /* delay amount decided by this */
149			n = snd_pcm_writei(vhd->pcm_playback,
150					   &vhd->simplebuf[vhd->rpos],
151					   ((vhd->wpos - vhd->rpos) &
152					    (sizeof(vhd->simplebuf) - 1)) / 2);
153			vhd->rpos =  (vhd->rpos + (n * 2)) &
154					(sizeof(vhd->simplebuf) - 1);
155		}
156
157		n = snd_pcm_readi(vhd->pcm_capture, &vhd->simplebuf[vhd->wpos],
158				  (sizeof(vhd->simplebuf) - vhd->wpos) / 2);
159		lwsl_notice("LWS_CALLBACK_RAW_RX_FILE: %d samples\n", n);
160		vhd->times++;
161
162		vhd->wpos = (vhd->wpos + (n * 2)) & (sizeof(vhd->simplebuf) - 1);
163		break;
164
165	default:
166		break;
167	}
168
169	return 0;
170}
171
172static struct lws_protocols protocols[] = {
173	{ "lws-audio-test", callback_raw_test, 0, 0 },
174	LWS_PROTOCOL_LIST_TERM
175};
176
177static int interrupted;
178
179void sigint_handler(int sig)
180{
181	interrupted = 1;
182}
183
184int main(int argc, const char **argv)
185{
186	struct lws_context_creation_info info;
187	struct lws_context *context;
188	int n = 0;
189
190	signal(SIGINT, sigint_handler);
191	memset(&info, 0, sizeof info);
192	lws_cmdline_option_handle_builtin(argc, argv, &info);
193
194	lwsl_user("LWS minimal raw audio\n");
195
196	info.port = CONTEXT_PORT_NO_LISTEN_SERVER;
197	info.protocols = protocols;
198
199	context = lws_create_context(&info);
200	if (!context) {
201		lwsl_err("lws init failed\n");
202		return 1;
203	}
204
205	while (n >= 0 && !interrupted)
206		n = lws_service(context, 0);
207
208	lws_context_destroy(context);
209
210	return 0;
211}
212