]> git.0d.be Git - jackwsmeter.git/blob - jackwsmeter.c
update NEWS file with new change
[jackwsmeter.git] / jackwsmeter.c
1 /*
2  * jackwsmeter - jack meter over websockets
3  *
4  * Copyright (C) 2014 Frederic Peters <fpeters@0d.be>
5  *
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.
10  *
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.
15  *
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/>.
19  */
20
21 /*
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
27  */
28
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>
38
39 #include <signal.h>
40
41 #include <libwebsockets.h>
42
43 #include <jack/jack.h>
44
45 int max_poll_elements;
46
47 struct pollfd *pollfds;
48 int *fd_lookup;
49 int count_pollfds;
50 int num_meters = 0;
51
52 int force_exit = 0;
53
54 #define MAX_METERS 20
55
56 float bias = 1.0f;
57 float peaks[MAX_METERS];
58 float sent_peaks[MAX_METERS];
59
60 jack_port_t *input_ports[MAX_METERS];
61 jack_client_t *client = NULL;
62
63
64 /* Read and reset the recent peak sample */
65 static void read_peaks()
66 {
67         memcpy(sent_peaks, peaks, sizeof(peaks));
68         memset(peaks, 0, sizeof(peaks));
69 }
70
71
72 /* this protocol server (always the first one) just knows how to do HTTP */
73
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)
78 {
79         int m;
80         char *html_filepath;
81 #ifdef LWS_13
82         struct libwebsocket_pollargs *pa = (struct libwebsocket_pollargs *)in;
83 #else
84         int fd = (int)(long)user;
85 #endif
86
87         switch (reason) {
88         case LWS_CALLBACK_HTTP:
89                 html_filepath = malloc(strlen(DATADIR "/jackwsmeter.html") + 1);
90                 strcpy(html_filepath, DATADIR "jackwsmeter.html\n\n");
91                 if (access(html_filepath, F_OK) != 0) {
92                         /* it doesn't exist in the system directory, fall back
93                          * on the current directory. */
94                         strcpy(html_filepath, "jackwsmeter.html");
95                 }
96 #ifdef LWS_13
97                 if (libwebsockets_serve_http_file(context, wsi, html_filepath, "text/html", NULL)) {
98 #else
99                 if (libwebsockets_serve_http_file(context, wsi, html_filepath, "text/html")) {
100 #endif
101                         free(html_filepath);
102                         return 1; /* through completion or error, close the socket */
103                 }
104                 free(html_filepath);
105
106                 break;
107
108         case LWS_CALLBACK_HTTP_FILE_COMPLETION:
109                 return 1;
110
111         case LWS_CALLBACK_ADD_POLL_FD:
112
113                 if (count_pollfds >= max_poll_elements) {
114                         lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n");
115                         return 1;
116                 }
117
118 #ifdef LWS_13
119                 fd_lookup[pa->fd] = count_pollfds;
120                 pollfds[count_pollfds].fd = pa->fd;
121                 pollfds[count_pollfds].events = pa->events;
122 #else
123                 fd_lookup[fd] = count_pollfds;
124                 pollfds[count_pollfds].fd = fd;
125                 pollfds[count_pollfds].events = (int)(long)len;
126 #endif
127                 pollfds[count_pollfds++].revents = 0;
128                 break;
129
130         case LWS_CALLBACK_DEL_POLL_FD:
131                 if (!--count_pollfds)
132                         break;
133 #ifdef LWS_13
134                 m = fd_lookup[pa->fd];
135 #else
136                 m = fd_lookup[fd];
137 #endif
138                 /* have the last guy take up the vacant slot */
139                 pollfds[m] = pollfds[count_pollfds];
140                 fd_lookup[pollfds[count_pollfds].fd] = m;
141                 break;
142
143 #ifdef LWS_13
144         case LWS_CALLBACK_CHANGE_MODE_POLL_FD:
145                 pollfds[fd_lookup[pa->fd]].events = pa->events;
146                 break;
147 #else
148         case LWS_CALLBACK_SET_MODE_POLL_FD:
149                 pollfds[fd_lookup[fd]].events |= (int)(long)len;
150                 break;
151
152         case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
153                 pollfds[fd_lookup[fd]].events &= ~(int)(long)len;
154                 break;
155 #endif
156
157         default:
158                 break;
159         }
160
161         return 0;
162 }
163
164 static int
165 callback_meter(struct libwebsocket_context *context,
166                 struct libwebsocket *wsi,
167                 enum libwebsocket_callback_reasons reason,
168                 void *user, void *in, size_t len)
169 {
170         int n;
171         int i;
172         float db;
173         char one_peak[100];
174         char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 + LWS_SEND_BUFFER_POST_PADDING];
175         char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
176
177         switch (reason) {
178
179         case LWS_CALLBACK_ESTABLISHED:
180                 break;
181
182         case LWS_CALLBACK_SERVER_WRITEABLE:
183                 p[0] = '\0';
184                 for (i=0; i<num_meters; i++) {
185                         db = 20.0f * log10f(sent_peaks[i] * bias);
186                         snprintf(one_peak, 100, "%f ", db);
187                         strcat((char*)p, one_peak);
188                 }
189                 n = strlen(p);
190                 p[n-1] = '\0'; /* remove trailing space */
191
192                 n = libwebsocket_write(wsi, (unsigned char*)p, n, LWS_WRITE_TEXT);
193                 if (n < 0) {
194                         lwsl_err("ERROR %d writing to socket\n", n);
195                         return 1;
196                 }
197                 break;
198
199         default:
200                 break;
201         }
202
203         return 0;
204 }
205
206 /* list of supported protocols and callbacks */
207
208 static struct libwebsocket_protocols protocols[] = {
209         /* first protocol must always be HTTP handler */
210
211         {
212                 "http-only",            /* name */
213                 callback_http,          /* callback */
214                 0,                      /* per_session_data_size */
215                 0,                      /* max frame size / rx buffer */
216         },
217         {
218                 "jack-wsmeter-protocol",
219                 callback_meter,
220                 0,
221                 0,
222         },
223         { NULL, NULL, 0, 0 } /* terminator */
224 };
225
226 void sighandler(int sig)
227 {
228         force_exit = 1;
229 }
230
231 static struct option options[] = {
232         { "help",       no_argument,            NULL, 'h' },
233         { "debug",      required_argument,      NULL, 'd' },
234         { "port",       required_argument,      NULL, 'p' },
235         { "ssl-cert",   required_argument,      NULL, 'E' },
236         { "ssl-key",    required_argument,      NULL, 'k' },
237         { "interface",  required_argument,      NULL, 'i' },
238 #ifndef LWS_NO_DAEMONIZE
239         { "daemonize",  no_argument,            NULL, 'D' },
240 #endif
241         { "name",       required_argument,      NULL, 'n' },
242         { NULL, 0, 0, 0 }
243 };
244
245
246 /* Callback called by JACK when audio is available.
247    Stores value of peak sample */
248 static int process_peak(jack_nframes_t nframes, void *arg)
249 {
250         unsigned int i, port;
251
252         for (port = 0; port < num_meters; port++) {
253                 jack_default_audio_sample_t *in;
254
255                 /* just incase the port isn't registered yet */
256                 if (input_ports[port] == 0) {
257                         break;
258                 }
259
260                 in = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[port], nframes);
261
262                 for (i = 0; i < nframes; i++) {
263                         const float s = fabs(in[i]);
264                         if (s > peaks[port]) {
265                                 peaks[port] = s;
266                         }
267                 }
268         }
269
270         return 0;
271 }
272
273
274 /* Close down JACK when exiting */
275 static void cleanup()
276 {
277         const char **all_ports;
278         unsigned int i, j;
279
280         lwsl_debug("cleanup()\n");
281
282         for (i=0; i<num_meters; i++) {
283                 all_ports = jack_port_get_all_connections(client, input_ports[i]);
284
285                 for (j=0; all_ports && all_ports[j]; j++) {
286                         jack_disconnect(client, all_ports[j], jack_port_name(input_ports[i]));
287                 }
288         }
289
290         /* Leave the jack graph */
291         jack_client_close(client);
292
293         closelog();
294 }
295
296
297 int main(int argc, char **argv)
298 {
299         int n = 0;
300         struct libwebsocket_context *context;
301         int opts = 0;
302         char interface_name[128] = "";
303         char jack_name[128] = "wsmeter";
304         const char *iface = NULL;
305         int syslog_options = LOG_PID | LOG_PERROR;
306         unsigned int oldus = 0;
307         struct lws_context_creation_info info;
308
309         int debug_level = 7;
310 #ifndef LWS_NO_DAEMONIZE
311         int daemonize = 0;
312 #endif
313
314         jack_status_t status;
315
316         memset(&info, 0, sizeof info);
317         info.port = 7681;
318
319         while (n >= 0) {
320                 n = getopt_long(argc, argv, "ci:hsp:d:Dn:", options, NULL);
321                 if (n < 0)
322                         continue;
323                 switch (n) {
324 #ifndef LWS_NO_DAEMONIZE
325                 case 'D':
326                         daemonize = 1;
327                         syslog_options &= ~LOG_PERROR;
328                         break;
329 #endif
330                 case 'd':
331                         debug_level = atoi(optarg);
332                         break;
333                 case 'E':
334                         info.ssl_cert_filepath = strdup(optarg);
335                         break;
336                 case 'k':
337                         info.ssl_private_key_filepath = strdup(optarg);
338                         break;
339                 case 'p':
340                         info.port = atoi(optarg);
341                         break;
342                 case 'i':
343                         strncpy(interface_name, optarg, sizeof interface_name);
344                         interface_name[(sizeof interface_name) - 1] = '\0';
345                         iface = interface_name;
346                         break;
347                 case 'n':
348                         strncpy(jack_name, optarg, sizeof jack_name);
349                         jack_name[(sizeof jack_name) - 1] = '\0';
350                         break;
351                 case 'h':
352                         fprintf(stderr, "Usage: jackwsserver "
353                                         "[--port=<p>] [--ssl-cert FILEPATH] [--ssl-key FILEPATH] "
354                                         "[-d <log bitfield>] <port>+\n");
355                         exit(1);
356                 }
357         }
358
359 #if !defined(LWS_NO_DAEMONIZE)
360         /* 
361          * normally lock path would be /var/lock/jwsm or similar, to
362          * simplify getting started without having to take care about
363          * permissions or running as root, set to /tmp/.jwsm-lock
364          */
365         if (daemonize && lws_daemonize("/tmp/.jwsm-lock")) {
366                 fprintf(stderr, "Failed to daemonize\n");
367                 return 1;
368         }
369 #endif
370
371         signal(SIGINT, sighandler);
372
373         /* we will only try to log things according to our debug_level */
374         setlogmask(LOG_UPTO (LOG_DEBUG));
375         openlog("jackwsmeter", syslog_options, LOG_DAEMON);
376
377         /* tell the library what debug level to emit and to send it to syslog */
378         lws_set_log_level(debug_level, lwsl_emit_syslog);
379
380         max_poll_elements = getdtablesize();
381         pollfds = malloc(max_poll_elements * sizeof (struct pollfd));
382         fd_lookup = malloc(max_poll_elements * sizeof (int));
383         if (pollfds == NULL || fd_lookup == NULL) {
384                 lwsl_err("Out of memory pollfds=%d\n", max_poll_elements);
385                 return -1;
386         }
387
388         info.iface = iface;
389         info.protocols = protocols;
390 #ifndef LWS_NO_EXTENSIONS
391         info.extensions = libwebsocket_get_internal_extensions();
392 #endif
393         info.gid = -1;
394         info.uid = -1;
395         info.options = opts;
396
397         // Register with Jack
398         if ((client = jack_client_open(jack_name, JackNullOption, &status)) == 0) {
399                 lwsl_err("Failed to start jack client: %d\n", status);
400                 exit(1);
401         }
402         lwsl_debug("Registering as '%s'.\n", jack_get_client_name(client));
403
404         // Register the cleanup function to be called when program exits
405         atexit(cleanup);
406
407         // Register the peak signal callback
408         jack_set_process_callback(client, process_peak, 0);
409
410
411         if (jack_activate(client)) {
412                 lwsl_err("Cannot activate client.\n");
413                 exit(1);
414         }
415
416         opts = optind;
417         num_meters = 0;
418         while (argv[opts]) {
419                 char in_name[255];
420                 jack_port_t *port;
421                 // Create our input port
422                 snprintf(in_name, 255, "in_%d", num_meters);
423                 if (!(input_ports[num_meters] = jack_port_register(client, in_name,
424                                                 JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0))) {
425                         lwsl_err("Cannot register input port '%s'.\n", in_name);
426                         exit(1);
427                 }
428
429                 port = jack_port_by_name(client, argv[opts]);
430                 if (port == NULL) {
431                         lwsl_err("Can't find port '%s'\n", argv[opts]);
432                 } else {
433                         if (jack_connect(client, jack_port_name(port), jack_port_name(input_ports[num_meters]))) {
434                                 lwsl_err("failed to connect to port '%s'\n", argv[opts]);
435                                 exit(1);
436                         }
437                 }
438                 num_meters += 1;
439                 if (num_meters == MAX_METERS) {
440                         lwsl_err("maximum number of meters (%d) reached.\n", MAX_METERS);
441                         break;
442                 }
443                 opts++;
444         }
445
446         if (num_meters == 0) {
447                 lwsl_err("You must specify at least one port, aborting.");
448                 exit(1);
449         }
450
451         context = libwebsocket_create_context(&info);
452         if (context == NULL) {
453                 lwsl_err("libwebsocket init failed\n");
454                 return -1;
455         }
456
457         n = 0;
458         while (n >= 0 && !force_exit) {
459                 struct timeval tv;
460
461                 gettimeofday(&tv, NULL);
462
463                 read_peaks();
464
465                 /*
466                  * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
467                  * live websocket connection as soon as it can take more packets
468                  * (usually immediately)
469                  */
470
471                 if (((unsigned int)tv.tv_usec - oldus) > 100000) {
472                         libwebsocket_callback_on_writable_all_protocol(&protocols[1]);
473                         oldus = tv.tv_usec;
474                 }
475
476                 n = poll(pollfds, count_pollfds, 25);
477                 if (n < 0)
478                         continue;
479
480
481                 if (n)
482                         for (n = 0; n < count_pollfds; n++)
483                                 if (pollfds[n].revents)
484                                         if (libwebsocket_service_fd(context,
485                                                                   &pollfds[n]) < 0)
486                                                 goto done;
487
488
489                 n = libwebsocket_service(context, 25);
490         }
491
492 done:
493         libwebsocket_context_destroy(context);
494
495         lwsl_notice("jackwsserver exited cleanly\n");
496
497         return 0;
498 }