remove obsolete closetest cmdline arg
[jackwsmeter.git] / jackwsmeter.c
blob6c866a646893bdc884059a5853186064962933a6
1 /*
2 * jackwsmeter - jack meter over websockets
4 * Copyright (C) 2014 Frederic Peters <fpeters@0d.be>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, see
18 * <http://www.gnu.org/licenses/>.
22 * based on code,
23 * from the libwebsockets test server: LGPL 2.1
24 * Copyright (C) 2010-2011 Andy Green <andy@warmcat.com>
25 * from jackmeter, GPL 2+
26 * Copyright (C) 2005 Nicholas J. Humfrey
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <getopt.h>
33 #include <string.h>
34 #include <sys/time.h>
35 #include <assert.h>
36 #include <syslog.h>
37 #include <math.h>
39 #include <signal.h>
41 #include <libwebsockets.h>
43 #include <jack/jack.h>
45 int max_poll_elements;
47 struct pollfd *pollfds;
48 int *fd_lookup;
49 int count_pollfds;
50 int num_meters = 0;
52 int force_exit = 0;
54 #define MAX_METERS 20
56 float bias = 1.0f;
57 float peaks[MAX_METERS];
58 float sent_peaks[MAX_METERS];
60 jack_port_t *input_ports[MAX_METERS];
61 jack_client_t *client = NULL;
64 /* Read and reset the recent peak sample */
65 static void read_peaks()
67 memcpy(sent_peaks, peaks, sizeof(peaks));
68 memset(peaks, 0, sizeof(peaks));
72 /* this protocol server (always the first one) just knows how to do HTTP */
74 static int callback_http(struct libwebsocket_context *context,
75 struct libwebsocket *wsi,
76 enum libwebsocket_callback_reasons reason, void *user,
77 void *in, size_t len)
79 int m;
80 int fd = (int)(long)user;
81 char *html_filepath;
83 switch (reason) {
84 case LWS_CALLBACK_HTTP:
85 html_filepath = malloc(strlen(DATADIR "/jackwsmeter.html") + 1);
86 strcpy(html_filepath, DATADIR "jackwsmeter.html\n\n");
87 if (access(html_filepath, F_OK) != 0) {
88 /* it doesn't exist in the system directory, fall back
89 * on the current directory. */
90 strcpy(html_filepath, "jackwsmeter.html");
92 if (libwebsockets_serve_http_file(context, wsi, html_filepath, "text/html")) {
93 free(html_filepath);
94 return 1; /* through completion or error, close the socket */
96 free(html_filepath);
98 break;
100 case LWS_CALLBACK_HTTP_FILE_COMPLETION:
101 return 1;
103 case LWS_CALLBACK_ADD_POLL_FD:
105 if (count_pollfds >= max_poll_elements) {
106 lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n");
107 return 1;
110 fd_lookup[fd] = count_pollfds;
111 pollfds[count_pollfds].fd = fd;
112 pollfds[count_pollfds].events = (int)(long)len;
113 pollfds[count_pollfds++].revents = 0;
114 break;
116 case LWS_CALLBACK_DEL_POLL_FD:
117 if (!--count_pollfds)
118 break;
119 m = fd_lookup[fd];
120 /* have the last guy take up the vacant slot */
121 pollfds[m] = pollfds[count_pollfds];
122 fd_lookup[pollfds[count_pollfds].fd] = m;
123 break;
125 case LWS_CALLBACK_SET_MODE_POLL_FD:
126 pollfds[fd_lookup[fd]].events |= (int)(long)len;
127 break;
129 case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
130 pollfds[fd_lookup[fd]].events &= ~(int)(long)len;
131 break;
133 default:
134 break;
137 return 0;
140 static int
141 callback_meter(struct libwebsocket_context *context,
142 struct libwebsocket *wsi,
143 enum libwebsocket_callback_reasons reason,
144 void *user, void *in, size_t len)
146 int n;
147 int i;
148 float db;
149 char one_peak[100];
150 char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 + LWS_SEND_BUFFER_POST_PADDING];
151 char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
153 switch (reason) {
155 case LWS_CALLBACK_ESTABLISHED:
156 break;
158 case LWS_CALLBACK_SERVER_WRITEABLE:
159 p[0] = '\0';
160 for (i=0; i<num_meters; i++) {
161 db = 20.0f * log10f(sent_peaks[i] * bias);
162 snprintf(one_peak, 100, "%f ", db);
163 strcat((char*)p, one_peak);
165 n = strlen(p) + 1;
166 n = libwebsocket_write(wsi, (unsigned char*)p, n, LWS_WRITE_TEXT);
167 if (n < 0) {
168 lwsl_err("ERROR %d writing to socket\n", n);
169 return 1;
171 break;
173 default:
174 break;
177 return 0;
180 /* list of supported protocols and callbacks */
182 static struct libwebsocket_protocols protocols[] = {
183 /* first protocol must always be HTTP handler */
186 "http-only", /* name */
187 callback_http, /* callback */
188 0, /* per_session_data_size */
189 0, /* max frame size / rx buffer */
192 "jack-wsmeter-protocol",
193 callback_meter,
197 { NULL, NULL, 0, 0 } /* terminator */
200 void sighandler(int sig)
202 force_exit = 1;
205 static struct option options[] = {
206 { "help", no_argument, NULL, 'h' },
207 { "debug", required_argument, NULL, 'd' },
208 { "port", required_argument, NULL, 'p' },
209 { "ssl-cert", required_argument, NULL, 'E' },
210 { "ssl-key", required_argument, NULL, 'k' },
211 { "interface", required_argument, NULL, 'i' },
212 #ifndef LWS_NO_DAEMONIZE
213 { "daemonize", no_argument, NULL, 'D' },
214 #endif
215 { "name", required_argument, NULL, 'n' },
216 { NULL, 0, 0, 0 }
220 /* Callback called by JACK when audio is available.
221 Stores value of peak sample */
222 static int process_peak(jack_nframes_t nframes, void *arg)
224 unsigned int i, port;
226 for (port = 0; port < num_meters; port++) {
227 jack_default_audio_sample_t *in;
229 /* just incase the port isn't registered yet */
230 if (input_ports[port] == 0) {
231 break;
234 in = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[port], nframes);
236 for (i = 0; i < nframes; i++) {
237 const float s = fabs(in[i]);
238 if (s > peaks[port]) {
239 peaks[port] = s;
244 return 0;
248 /* Close down JACK when exiting */
249 static void cleanup()
251 const char **all_ports;
252 unsigned int i, j;
254 lwsl_debug("cleanup()\n");
256 for (i=0; i<num_meters; i++) {
257 all_ports = jack_port_get_all_connections(client, input_ports[i]);
259 for (j=0; all_ports && all_ports[j]; j++) {
260 jack_disconnect(client, all_ports[j], jack_port_name(input_ports[i]));
264 /* Leave the jack graph */
265 jack_client_close(client);
267 closelog();
271 int main(int argc, char **argv)
273 int n = 0;
274 struct libwebsocket_context *context;
275 int opts = 0;
276 char interface_name[128] = "";
277 char jack_name[128] = "wsmeter";
278 const char *iface = NULL;
279 int syslog_options = LOG_PID | LOG_PERROR;
280 unsigned int oldus = 0;
281 struct lws_context_creation_info info;
283 int debug_level = 7;
284 #ifndef LWS_NO_DAEMONIZE
285 int daemonize = 0;
286 #endif
288 jack_status_t status;
290 memset(&info, 0, sizeof info);
291 info.port = 7681;
293 while (n >= 0) {
294 n = getopt_long(argc, argv, "ci:hsp:d:Dn:", options, NULL);
295 if (n < 0)
296 continue;
297 switch (n) {
298 #ifndef LWS_NO_DAEMONIZE
299 case 'D':
300 daemonize = 1;
301 syslog_options &= ~LOG_PERROR;
302 break;
303 #endif
304 case 'd':
305 debug_level = atoi(optarg);
306 break;
307 case 'E':
308 info.ssl_cert_filepath = strdup(optarg);
309 break;
310 case 'k':
311 info.ssl_private_key_filepath = strdup(optarg);
312 break;
313 case 'p':
314 info.port = atoi(optarg);
315 break;
316 case 'i':
317 strncpy(interface_name, optarg, sizeof interface_name);
318 interface_name[(sizeof interface_name) - 1] = '\0';
319 iface = interface_name;
320 break;
321 case 'n':
322 strncpy(jack_name, optarg, sizeof jack_name);
323 jack_name[(sizeof jack_name) - 1] = '\0';
324 break;
325 case 'h':
326 fprintf(stderr, "Usage: jackwsserver "
327 "[--port=<p>] [--ssl-cert FILEPATH] [--ssl-key FILEPATH] "
328 "[-d <log bitfield>] <port>+\n");
329 exit(1);
333 #if !defined(LWS_NO_DAEMONIZE)
335 * normally lock path would be /var/lock/jwsm or similar, to
336 * simplify getting started without having to take care about
337 * permissions or running as root, set to /tmp/.jwsm-lock
339 if (daemonize && lws_daemonize("/tmp/.jwsm-lock")) {
340 fprintf(stderr, "Failed to daemonize\n");
341 return 1;
343 #endif
345 signal(SIGINT, sighandler);
347 /* we will only try to log things according to our debug_level */
348 setlogmask(LOG_UPTO (LOG_DEBUG));
349 openlog("jackwsmeter", syslog_options, LOG_DAEMON);
351 /* tell the library what debug level to emit and to send it to syslog */
352 lws_set_log_level(debug_level, lwsl_emit_syslog);
354 max_poll_elements = getdtablesize();
355 pollfds = malloc(max_poll_elements * sizeof (struct pollfd));
356 fd_lookup = malloc(max_poll_elements * sizeof (int));
357 if (pollfds == NULL || fd_lookup == NULL) {
358 lwsl_err("Out of memory pollfds=%d\n", max_poll_elements);
359 return -1;
362 info.iface = iface;
363 info.protocols = protocols;
364 #ifndef LWS_NO_EXTENSIONS
365 info.extensions = libwebsocket_get_internal_extensions();
366 #endif
367 info.gid = -1;
368 info.uid = -1;
369 info.options = opts;
371 // Register with Jack
372 if ((client = jack_client_open(jack_name, JackNullOption, &status)) == 0) {
373 lwsl_err("Failed to start jack client: %d\n", status);
374 exit(1);
376 lwsl_debug("Registering as '%s'.\n", jack_get_client_name(client));
378 // Register the cleanup function to be called when program exits
379 atexit(cleanup);
381 // Register the peak signal callback
382 jack_set_process_callback(client, process_peak, 0);
385 if (jack_activate(client)) {
386 lwsl_err("Cannot activate client.\n");
387 exit(1);
390 opts = optind;
391 num_meters = 0;
392 while (argv[opts]) {
393 char in_name[255];
394 jack_port_t *port;
395 // Create our input port
396 snprintf(in_name, 255, "in_%d", num_meters);
397 if (!(input_ports[num_meters] = jack_port_register(client, in_name,
398 JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0))) {
399 lwsl_err("Cannot register input port '%s'.\n", in_name);
400 exit(1);
403 port = jack_port_by_name(client, argv[opts]);
404 if (port == NULL) {
405 lwsl_err("Can't find port '%s'\n", argv[opts]);
406 } else {
407 if (jack_connect(client, jack_port_name(port), jack_port_name(input_ports[num_meters]))) {
408 lwsl_err("failed to connect to port '%s'\n", argv[opts]);
409 exit(1);
412 num_meters += 1;
413 if (num_meters == MAX_METERS) {
414 lwsl_err("maximum number of meters (%d) reached.\n", MAX_METERS);
415 break;
417 opts++;
420 if (num_meters == 0) {
421 lwsl_err("You must specify at least one port, aborting.");
422 exit(1);
425 context = libwebsocket_create_context(&info);
426 if (context == NULL) {
427 lwsl_err("libwebsocket init failed\n");
428 return -1;
431 n = 0;
432 while (n >= 0 && !force_exit) {
433 struct timeval tv;
435 gettimeofday(&tv, NULL);
437 read_peaks();
440 * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
441 * live websocket connection as soon as it can take more packets
442 * (usually immediately)
445 if (((unsigned int)tv.tv_usec - oldus) > 100000) {
446 libwebsocket_callback_on_writable_all_protocol(&protocols[1]);
447 oldus = tv.tv_usec;
450 n = poll(pollfds, count_pollfds, 25);
451 if (n < 0)
452 continue;
455 if (n)
456 for (n = 0; n < count_pollfds; n++)
457 if (pollfds[n].revents)
458 if (libwebsocket_service_fd(context,
459 &pollfds[n]) < 0)
460 goto done;
463 n = libwebsocket_service(context, 25);
466 done:
467 libwebsocket_context_destroy(context);
469 lwsl_notice("jackwsserver exited cleanly\n");
471 return 0;