]> git.0d.be Git - jackwsmeter.git/blob - jackwsmeter.c
wsmeter, 1st draft
[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         unsigned char one_peak[100];
139         unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 +
140                                                   LWS_SEND_BUFFER_POST_PADDING];
141         unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
142
143         switch (reason) {
144
145         case LWS_CALLBACK_ESTABLISHED:
146                 break;
147
148         case LWS_CALLBACK_SERVER_WRITEABLE:
149                 p[0] = '\0';
150                 for (i=0; i<num_meters; i++) {
151                         db = 20.0f * log10f(sent_peaks[i] * bias);
152                         snprintf(one_peak, 100, "%f ", db);
153                         strcat((char*)p, one_peak);
154                 }
155                 n = strlen(p) + 1;
156                 n = libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
157                 if (n < 0) {
158                         lwsl_err("ERROR %d writing to socket\n", n);
159                         return 1;
160                 }
161                 break;
162
163         default:
164                 break;
165         }
166
167         return 0;
168 }
169
170 /* list of supported protocols and callbacks */
171
172 static struct libwebsocket_protocols protocols[] = {
173         /* first protocol must always be HTTP handler */
174
175         {
176                 "http-only",            /* name */
177                 callback_http,          /* callback */
178                 0,                      /* per_session_data_size */
179                 0,                      /* max frame size / rx buffer */
180         },
181         {
182                 "jack-wsmeter-protocol",
183                 callback_meter,
184                 0,
185                 0,
186         },
187         { NULL, NULL, 0, 0 } /* terminator */
188 };
189
190 void sighandler(int sig)
191 {
192         force_exit = 1;
193 }
194
195 static struct option options[] = {
196         { "help",       no_argument,            NULL, 'h' },
197         { "debug",      required_argument,      NULL, 'd' },
198         { "port",       required_argument,      NULL, 'p' },
199         { "ssl",        no_argument,            NULL, 's' },
200         { "interface",  required_argument,      NULL, 'i' },
201         { "closetest",  no_argument,            NULL, 'c' },
202 #ifndef LWS_NO_DAEMONIZE
203         { "daemonize",  no_argument,            NULL, 'D' },
204 #endif
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         jack_default_audio_sample_t *in;
214         unsigned int i, port;
215
216         for (port = 0; port < num_meters; port++) {
217                 jack_default_audio_sample_t *in, *out;
218
219                 /* just incase the port isn't registered yet */
220                 if (input_ports[port] == 0) {
221                         break;
222                 }
223
224                 in = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[port], nframes);
225
226                 for (i = 0; i < nframes; i++) {
227                         const float s = fabs(in[i]);
228                         if (s > peaks[port]) {
229                                 peaks[port] = s;
230                         }
231                 }
232         }
233
234         return 0;
235 }
236
237
238 /* Close down JACK when exiting */
239 static void cleanup()
240 {
241         const char **all_ports;
242         unsigned int i;
243
244         fprintf(stderr,"cleanup()\n");
245
246         for (i=0; i<num_meters; i++) {
247                 all_ports = jack_port_get_all_connections(client, input_ports[i]);
248
249                 for (i=0; all_ports && all_ports[i]; i++) {
250                         jack_disconnect(client, all_ports[i], jack_port_name(input_ports[i]));
251                 }
252         }
253
254         /* Leave the jack graph */
255         jack_client_close(client);
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: test-server "
310                                         "[--port=<p>] [--ssl] "
311                                         "[-d <log bitfield>]\n");
312                         exit(1);
313                 }
314         }
315
316 #if !defined(LWS_NO_DAEMONIZE)
317         /* 
318          * normally lock path would be /var/lock/lwsts or similar, to
319          * simplify getting started without having to take care about
320          * permissions or running as root, set to /tmp/.lwsts-lock
321          */
322         if (daemonize && lws_daemonize("/tmp/.lwsts-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("lwsts", 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         lwsl_notice("libwebsockets test server - "
338                         "(C) Copyright 2010-2013 Andy Green <andy@warmcat.com> - "
339                                                     "licensed under LGPL2.1\n");
340
341         max_poll_elements = getdtablesize();
342         pollfds = malloc(max_poll_elements * sizeof (struct pollfd));
343         fd_lookup = malloc(max_poll_elements * sizeof (int));
344         if (pollfds == NULL || fd_lookup == NULL) {
345                 lwsl_err("Out of memory pollfds=%d\n", max_poll_elements);
346                 return -1;
347         }
348
349         info.iface = iface;
350         info.protocols = protocols;
351 #ifndef LWS_NO_EXTENSIONS
352         info.extensions = libwebsocket_get_internal_extensions();
353 #endif
354         if (!use_ssl) {
355                 info.ssl_cert_filepath = NULL;
356                 info.ssl_private_key_filepath = NULL;
357         } else {
358                 /*
359                 info.ssl_cert_filepath = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.pem";
360                 info.ssl_private_key_filepath = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.key.pem";
361                 */
362         }
363         info.gid = -1;
364         info.uid = -1;
365         info.options = opts;
366
367         // Register with Jack
368         if ((client = jack_client_open("wsmeter", JackNullOption, &status)) == 0) {
369                 fprintf(stderr, "Failed to start jack client: %d\n", status);
370                 exit(1);
371         }
372         fprintf(stderr,"Registering as '%s'.\n", jack_get_client_name( client ) );
373
374         // Register the cleanup function to be called when program exits
375         atexit( cleanup );
376
377         // Register the peak signal callback
378         jack_set_process_callback(client, process_peak, 0);
379
380
381         if (jack_activate(client)) {
382                 fprintf(stderr, "Cannot activate client.\n");
383                 exit(1);
384         }
385
386         opts = 1;
387         num_meters = 0;
388         while (argv[opts]) {
389                 char in_name[255];
390                 jack_port_t *port;
391                 // Create our input port
392                 snprintf(in_name, 255, "wsmeter_%d", num_meters);
393                 if (!(input_ports[num_meters] = jack_port_register(client, in_name,
394                                                 JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0))) {
395                         fprintf(stderr, "Cannot register input port 'meter'.\n");
396                         exit(1);
397                 }
398
399                 port = jack_port_by_name(client, argv[opts]);
400                 if (port == NULL) {
401                         fprintf(stderr, "Can't find port '%s'\n", argv[opts]);
402                 }
403                 if (jack_connect(client, jack_port_name(port), jack_port_name(input_ports[num_meters]))) {
404                         /* failed to connect, pass */
405                         fprintf(stderr, "failed to connect\n");
406                 }
407                 num_meters += 1;
408                 opts++;
409         }
410
411         context = libwebsocket_create_context(&info);
412         if (context == NULL) {
413                 lwsl_err("libwebsocket init failed\n");
414                 return -1;
415         }
416
417         n = 0;
418         while (n >= 0 && !force_exit) {
419                 struct timeval tv;
420
421                 gettimeofday(&tv, NULL);
422
423                 read_peaks();
424
425                 /*
426                  * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
427                  * live websocket connection using the DUMB_INCREMENT protocol,
428                  * as soon as it can take more packets (usually immediately)
429                  */
430
431                 if (((unsigned int)tv.tv_usec - oldus) > 100000) {
432                         libwebsocket_callback_on_writable_all_protocol(&protocols[1]);
433                         oldus = tv.tv_usec;
434                 }
435
436                 n = poll(pollfds, count_pollfds, 25);
437                 if (n < 0)
438                         continue;
439
440
441                 if (n)
442                         for (n = 0; n < count_pollfds; n++)
443                                 if (pollfds[n].revents)
444                                         if (libwebsocket_service_fd(context,
445                                                                   &pollfds[n]) < 0)
446                                                 goto done;
447
448
449                 n = libwebsocket_service(context, 25);
450         }
451
452 done:
453         libwebsocket_context_destroy(context);
454
455         lwsl_notice("libwebsockets-test-server exited cleanly\n");
456
457         closelog();
458
459         return 0;
460 }