]> git.0d.be Git - jackwsmeter.git/blob - jackwsmeter.c
ecbe1c33ad37ebc1bdcbe8750ad68488e6e7aefc
[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         { NULL, 0, 0, 0 }
205 };
206
207
208 /* Callback called by JACK when audio is available.
209    Stores value of peak sample */
210 static int process_peak(jack_nframes_t nframes, void *arg)
211 {
212         unsigned int i, port;
213
214         for (port = 0; port < num_meters; port++) {
215                 jack_default_audio_sample_t *in;
216
217                 /* just incase the port isn't registered yet */
218                 if (input_ports[port] == 0) {
219                         break;
220                 }
221
222                 in = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[port], nframes);
223
224                 for (i = 0; i < nframes; i++) {
225                         const float s = fabs(in[i]);
226                         if (s > peaks[port]) {
227                                 peaks[port] = s;
228                         }
229                 }
230         }
231
232         return 0;
233 }
234
235
236 /* Close down JACK when exiting */
237 static void cleanup()
238 {
239         const char **all_ports;
240         unsigned int i;
241
242         lwsl_debug("cleanup()\n");
243
244         for (i=0; i<num_meters; i++) {
245                 all_ports = jack_port_get_all_connections(client, input_ports[i]);
246
247                 for (i=0; all_ports && all_ports[i]; i++) {
248                         jack_disconnect(client, all_ports[i], jack_port_name(input_ports[i]));
249                 }
250         }
251
252         /* Leave the jack graph */
253         jack_client_close(client);
254
255         closelog();
256 }
257
258
259 int main(int argc, char **argv)
260 {
261         int n = 0;
262         int use_ssl = 0;
263         struct libwebsocket_context *context;
264         int opts = 0;
265         char interface_name[128] = "";
266         const char *iface = NULL;
267 #ifndef WIN32
268         int syslog_options = LOG_PID | LOG_PERROR;
269 #endif
270         unsigned int oldus = 0;
271         struct lws_context_creation_info info;
272
273         int debug_level = 7;
274 #ifndef LWS_NO_DAEMONIZE
275         int daemonize = 0;
276 #endif
277
278         jack_status_t status;
279
280         memset(&info, 0, sizeof info);
281         info.port = 7681;
282
283         while (n >= 0) {
284                 n = getopt_long(argc, argv, "ci:hsp:d:D", options, NULL);
285                 if (n < 0)
286                         continue;
287                 switch (n) {
288 #ifndef LWS_NO_DAEMONIZE
289                 case 'D':
290                         daemonize = 1;
291                         syslog_options &= ~LOG_PERROR;
292                         break;
293 #endif
294                 case 'd':
295                         debug_level = atoi(optarg);
296                         break;
297                 case 's':
298                         use_ssl = 1;
299                         break;
300                 case 'p':
301                         info.port = atoi(optarg);
302                         break;
303                 case 'i':
304                         strncpy(interface_name, optarg, sizeof interface_name);
305                         interface_name[(sizeof interface_name) - 1] = '\0';
306                         iface = interface_name;
307                         break;
308                 case 'h':
309                         fprintf(stderr, "Usage: jackwsserver "
310                                         "[--port=<p>] [--ssl] "
311                                         "[-d <log bitfield>] <port>+\n");
312                         exit(1);
313                 }
314         }
315
316 #if !defined(LWS_NO_DAEMONIZE)
317         /* 
318          * normally lock path would be /var/lock/jwsm or similar, to
319          * simplify getting started without having to take care about
320          * permissions or running as root, set to /tmp/.jwsm-lock
321          */
322         if (daemonize && lws_daemonize("/tmp/.jwsm-lock")) {
323                 fprintf(stderr, "Failed to daemonize\n");
324                 return 1;
325         }
326 #endif
327
328         signal(SIGINT, sighandler);
329
330         /* we will only try to log things according to our debug_level */
331         setlogmask(LOG_UPTO (LOG_DEBUG));
332         openlog("jackwsmeter", syslog_options, LOG_DAEMON);
333
334         /* tell the library what debug level to emit and to send it to syslog */
335         lws_set_log_level(debug_level, lwsl_emit_syslog);
336
337         max_poll_elements = getdtablesize();
338         pollfds = malloc(max_poll_elements * sizeof (struct pollfd));
339         fd_lookup = malloc(max_poll_elements * sizeof (int));
340         if (pollfds == NULL || fd_lookup == NULL) {
341                 lwsl_err("Out of memory pollfds=%d\n", max_poll_elements);
342                 return -1;
343         }
344
345         info.iface = iface;
346         info.protocols = protocols;
347 #ifndef LWS_NO_EXTENSIONS
348         info.extensions = libwebsocket_get_internal_extensions();
349 #endif
350         if (!use_ssl) {
351                 info.ssl_cert_filepath = NULL;
352                 info.ssl_private_key_filepath = NULL;
353         } else {
354                 /*
355                 info.ssl_cert_filepath = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.pem";
356                 info.ssl_private_key_filepath = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.key.pem";
357                 */
358         }
359         info.gid = -1;
360         info.uid = -1;
361         info.options = opts;
362
363         // Register with Jack
364         if ((client = jack_client_open("wsmeter", JackNullOption, &status)) == 0) {
365                 lwsl_err("Failed to start jack client: %d\n", status);
366                 exit(1);
367         }
368         lwsl_debug("Registering as '%s'.\n", jack_get_client_name( client ) );
369
370         // Register the cleanup function to be called when program exits
371         atexit( cleanup );
372
373         // Register the peak signal callback
374         jack_set_process_callback(client, process_peak, 0);
375
376
377         if (jack_activate(client)) {
378                 lwsl_err("Cannot activate client.\n");
379                 exit(1);
380         }
381
382         opts = 1;
383         num_meters = 0;
384         while (argv[opts]) {
385                 char in_name[255];
386                 jack_port_t *port;
387                 // Create our input port
388                 snprintf(in_name, 255, "wsmeter_%d", num_meters);
389                 if (!(input_ports[num_meters] = jack_port_register(client, in_name,
390                                                 JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0))) {
391                         lwsl_err("Cannot register input port '%s'.\n", in_name);
392                         exit(1);
393                 }
394
395                 port = jack_port_by_name(client, argv[opts]);
396                 if (port == NULL) {
397                         lwsl_err("Can't find port '%s'\n", argv[opts]);
398                 } else {
399                         if (jack_connect(client, jack_port_name(port), jack_port_name(input_ports[num_meters]))) {
400                                 lwsl_err("failed to connect to port '%s'\n", argv[opts]);
401                                 exit(1);
402                         }
403                 }
404                 num_meters += 1;
405                 opts++;
406         }
407
408         if (num_meters == 0) {
409                 lwsl_err("You must specify at least one port, aborting.");
410                 exit(1);
411         }
412
413         context = libwebsocket_create_context(&info);
414         if (context == NULL) {
415                 lwsl_err("libwebsocket init failed\n");
416                 return -1;
417         }
418
419         n = 0;
420         while (n >= 0 && !force_exit) {
421                 struct timeval tv;
422
423                 gettimeofday(&tv, NULL);
424
425                 read_peaks();
426
427                 /*
428                  * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
429                  * live websocket connection using the DUMB_INCREMENT protocol,
430                  * as soon as it can take more packets (usually immediately)
431                  */
432
433                 if (((unsigned int)tv.tv_usec - oldus) > 100000) {
434                         libwebsocket_callback_on_writable_all_protocol(&protocols[1]);
435                         oldus = tv.tv_usec;
436                 }
437
438                 n = poll(pollfds, count_pollfds, 25);
439                 if (n < 0)
440                         continue;
441
442
443                 if (n)
444                         for (n = 0; n < count_pollfds; n++)
445                                 if (pollfds[n].revents)
446                                         if (libwebsocket_service_fd(context,
447                                                                   &pollfds[n]) < 0)
448                                                 goto done;
449
450
451                 n = libwebsocket_service(context, 25);
452         }
453
454 done:
455         libwebsocket_context_destroy(context);
456
457         lwsl_notice("jackwsserver exited cleanly\n");
458
459         return 0;
460 }