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