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/>.
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
41 #include <libwebsockets.h>
43 #include <jack/jack.h>
45 int max_poll_elements;
47 struct pollfd *pollfds;
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,
80 int fd = (int)(long)user;
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 */
89 case LWS_CALLBACK_HTTP_FILE_COMPLETION:
92 case LWS_CALLBACK_ADD_POLL_FD:
94 if (count_pollfds >= max_poll_elements) {
95 lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n");
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;
105 case LWS_CALLBACK_DEL_POLL_FD:
106 if (!--count_pollfds)
109 /* have the last guy take up the vacant slot */
110 pollfds[m] = pollfds[count_pollfds];
111 fd_lookup[pollfds[count_pollfds].fd] = m;
114 case LWS_CALLBACK_SET_MODE_POLL_FD:
115 pollfds[fd_lookup[fd]].events |= (int)(long)len;
118 case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
119 pollfds[fd_lookup[fd]].events &= ~(int)(long)len;
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)
139 char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 + LWS_SEND_BUFFER_POST_PADDING];
140 char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
144 case LWS_CALLBACK_ESTABLISHED:
147 case LWS_CALLBACK_SERVER_WRITEABLE:
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);
155 n = libwebsocket_write(wsi, (unsigned char*)p, n, LWS_WRITE_TEXT);
157 lwsl_err("ERROR %d writing to socket\n", n);
169 /* list of supported protocols and callbacks */
171 static struct libwebsocket_protocols protocols[] = {
172 /* first protocol must always be HTTP handler */
175 "http-only", /* name */
176 callback_http, /* callback */
177 0, /* per_session_data_size */
178 0, /* max frame size / rx buffer */
181 "jack-wsmeter-protocol",
186 { NULL, NULL, 0, 0 } /* terminator */
189 void sighandler(int sig)
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' },
204 { "name", required_argument, NULL, 'n' },
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)
213 unsigned int i, port;
215 for (port = 0; port < num_meters; port++) {
216 jack_default_audio_sample_t *in;
218 /* just incase the port isn't registered yet */
219 if (input_ports[port] == 0) {
223 in = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[port], nframes);
225 for (i = 0; i < nframes; i++) {
226 const float s = fabs(in[i]);
227 if (s > peaks[port]) {
237 /* Close down JACK when exiting */
238 static void cleanup()
240 const char **all_ports;
243 lwsl_debug("cleanup()\n");
245 for (i=0; i<num_meters; i++) {
246 all_ports = jack_port_get_all_connections(client, input_ports[i]);
248 for (j=0; all_ports && all_ports[j]; j++) {
249 jack_disconnect(client, all_ports[j], jack_port_name(input_ports[i]));
253 /* Leave the jack graph */
254 jack_client_close(client);
260 int main(int argc, char **argv)
264 struct libwebsocket_context *context;
266 char interface_name[128] = "";
267 char jack_name[128] = "wsmeter";
268 const char *iface = NULL;
270 int syslog_options = LOG_PID | LOG_PERROR;
272 unsigned int oldus = 0;
273 struct lws_context_creation_info info;
276 #ifndef LWS_NO_DAEMONIZE
280 jack_status_t status;
282 memset(&info, 0, sizeof info);
286 n = getopt_long(argc, argv, "ci:hsp:d:Dn:", options, NULL);
290 #ifndef LWS_NO_DAEMONIZE
293 syslog_options &= ~LOG_PERROR;
297 debug_level = atoi(optarg);
303 info.port = atoi(optarg);
306 strncpy(interface_name, optarg, sizeof interface_name);
307 interface_name[(sizeof interface_name) - 1] = '\0';
308 iface = interface_name;
311 strncpy(jack_name, optarg, sizeof jack_name);
312 jack_name[(sizeof jack_name) - 1] = '\0';
315 fprintf(stderr, "Usage: jackwsserver "
316 "[--port=<p>] [--ssl] "
317 "[-d <log bitfield>] <port>+\n");
322 #if !defined(LWS_NO_DAEMONIZE)
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
328 if (daemonize && lws_daemonize("/tmp/.jwsm-lock")) {
329 fprintf(stderr, "Failed to daemonize\n");
334 signal(SIGINT, sighandler);
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);
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);
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);
352 info.protocols = protocols;
353 #ifndef LWS_NO_EXTENSIONS
354 info.extensions = libwebsocket_get_internal_extensions();
357 info.ssl_cert_filepath = NULL;
358 info.ssl_private_key_filepath = NULL;
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";
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);
374 lwsl_debug("Registering as '%s'.\n", jack_get_client_name( client ) );
376 // Register the cleanup function to be called when program exits
379 // Register the peak signal callback
380 jack_set_process_callback(client, process_peak, 0);
383 if (jack_activate(client)) {
384 lwsl_err("Cannot activate client.\n");
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);
401 port = jack_port_by_name(client, argv[opts]);
403 lwsl_err("Can't find port '%s'\n", argv[opts]);
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]);
414 if (num_meters == 0) {
415 lwsl_err("You must specify at least one port, aborting.");
419 context = libwebsocket_create_context(&info);
420 if (context == NULL) {
421 lwsl_err("libwebsocket init failed\n");
426 while (n >= 0 && !force_exit) {
429 gettimeofday(&tv, NULL);
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)
439 if (((unsigned int)tv.tv_usec - oldus) > 100000) {
440 libwebsocket_callback_on_writable_all_protocol(&protocols[1]);
444 n = poll(pollfds, count_pollfds, 25);
450 for (n = 0; n < count_pollfds; n++)
451 if (pollfds[n].revents)
452 if (libwebsocket_service_fd(context,
457 n = libwebsocket_service(context, 25);
461 libwebsocket_context_destroy(context);
463 lwsl_notice("jackwsserver exited cleanly\n");