]> git.0d.be Git - jackwsmeter.git/commitdiff
wsmeter, 1st draft
authorFrédéric Péters <fpeters@0d.be>
Tue, 1 Jul 2014 19:30:38 +0000 (21:30 +0200)
committerFrédéric Péters <fpeters@0d.be>
Wed, 2 Jul 2014 16:43:19 +0000 (18:43 +0200)
Makefile [new file with mode: 0644]
jackwsmeter.c [new file with mode: 0644]
jackwsmeter.html [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..4730b97
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,4 @@
+jackwsmeter: jackwsmeter.c
+       gcc jackwsmeter.c -o jackwsmeter -lwebsockets `pkg-config --cflags --libs jack` -g -lm
+
+all: jackwsmeter
diff --git a/jackwsmeter.c b/jackwsmeter.c
new file mode 100644 (file)
index 0000000..73fd598
--- /dev/null
@@ -0,0 +1,460 @@
+/*
+ * jackwsmeter - jack meter over websockets
+ *
+ * Copyright (C) 2014 Frederic Peters <fpeters@0d.be>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * based on code,
+ * from the libwebsockets test server: LGPL 2.1
+ *   Copyright (C) 2010-2011 Andy Green <andy@warmcat.com>
+ * from jackmeter, GPL 2+
+ *   Copyright (C) 2005  Nicholas J. Humfrey
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <string.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <syslog.h>
+#include <math.h>
+
+#include <signal.h>
+
+#include <libwebsockets.h>
+
+#include <jack/jack.h>
+
+int max_poll_elements;
+
+struct pollfd *pollfds;
+int *fd_lookup;
+int count_pollfds;
+int num_meters = 0;
+
+int force_exit = 0;
+
+#define MAX_METERS 10
+
+float bias = 1.0f;
+float peaks[MAX_METERS];
+float sent_peaks[MAX_METERS];
+
+jack_port_t *input_ports[MAX_METERS];
+jack_client_t *client = NULL;
+
+
+/* Read and reset the recent peak sample */
+static void read_peaks()
+{
+       memcpy(sent_peaks, peaks, sizeof(peaks));
+       memset(peaks, 0, sizeof(peaks));
+}
+
+
+/* this protocol server (always the first one) just knows how to do HTTP */
+
+static int callback_http(struct libwebsocket_context *context,
+               struct libwebsocket *wsi,
+               enum libwebsocket_callback_reasons reason, void *user,
+                                                          void *in, size_t len)
+{
+       int m;
+       int fd = (int)(long)user;
+
+       switch (reason) {
+       case LWS_CALLBACK_HTTP:
+               if (libwebsockets_serve_http_file(context, wsi, "jackwsmeter.html", "text/html"))
+                       return 1; /* through completion or error, close the socket */
+
+               break;
+
+       case LWS_CALLBACK_HTTP_FILE_COMPLETION:
+               return 1;
+
+       case LWS_CALLBACK_ADD_POLL_FD:
+
+               if (count_pollfds >= max_poll_elements) {
+                       lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n");
+                       return 1;
+               }
+
+               fd_lookup[fd] = count_pollfds;
+               pollfds[count_pollfds].fd = fd;
+               pollfds[count_pollfds].events = (int)(long)len;
+               pollfds[count_pollfds++].revents = 0;
+               break;
+
+       case LWS_CALLBACK_DEL_POLL_FD:
+               if (!--count_pollfds)
+                       break;
+               m = fd_lookup[fd];
+               /* have the last guy take up the vacant slot */
+               pollfds[m] = pollfds[count_pollfds];
+               fd_lookup[pollfds[count_pollfds].fd] = m;
+               break;
+
+       case LWS_CALLBACK_SET_MODE_POLL_FD:
+               pollfds[fd_lookup[fd]].events |= (int)(long)len;
+               break;
+
+       case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
+               pollfds[fd_lookup[fd]].events &= ~(int)(long)len;
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static int
+callback_meter(struct libwebsocket_context *context,
+               struct libwebsocket *wsi,
+               enum libwebsocket_callback_reasons reason,
+               void *user, void *in, size_t len)
+{
+       int n;
+       int i;
+       float db;
+       unsigned char one_peak[100];
+       unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 +
+                                                 LWS_SEND_BUFFER_POST_PADDING];
+       unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
+
+       switch (reason) {
+
+       case LWS_CALLBACK_ESTABLISHED:
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+               p[0] = '\0';
+               for (i=0; i<num_meters; i++) {
+                       db = 20.0f * log10f(sent_peaks[i] * bias);
+                       snprintf(one_peak, 100, "%f ", db);
+                       strcat((char*)p, one_peak);
+               }
+               n = strlen(p) + 1;
+               n = libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
+               if (n < 0) {
+                       lwsl_err("ERROR %d writing to socket\n", n);
+                       return 1;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+/* list of supported protocols and callbacks */
+
+static struct libwebsocket_protocols protocols[] = {
+       /* first protocol must always be HTTP handler */
+
+       {
+               "http-only",            /* name */
+               callback_http,          /* callback */
+               0,                      /* per_session_data_size */
+               0,                      /* max frame size / rx buffer */
+       },
+       {
+               "jack-wsmeter-protocol",
+               callback_meter,
+               0,
+               0,
+       },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+void sighandler(int sig)
+{
+       force_exit = 1;
+}
+
+static struct option options[] = {
+       { "help",       no_argument,            NULL, 'h' },
+       { "debug",      required_argument,      NULL, 'd' },
+       { "port",       required_argument,      NULL, 'p' },
+       { "ssl",        no_argument,            NULL, 's' },
+       { "interface",  required_argument,      NULL, 'i' },
+       { "closetest",  no_argument,            NULL, 'c' },
+#ifndef LWS_NO_DAEMONIZE
+       { "daemonize",  no_argument,            NULL, 'D' },
+#endif
+       { NULL, 0, 0, 0 }
+};
+
+
+/* Callback called by JACK when audio is available.
+   Stores value of peak sample */
+static int process_peak(jack_nframes_t nframes, void *arg)
+{
+       jack_default_audio_sample_t *in;
+       unsigned int i, port;
+
+       for (port = 0; port < num_meters; port++) {
+               jack_default_audio_sample_t *in, *out;
+
+               /* just incase the port isn't registered yet */
+               if (input_ports[port] == 0) {
+                       break;
+               }
+
+               in = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[port], nframes);
+
+               for (i = 0; i < nframes; i++) {
+                       const float s = fabs(in[i]);
+                       if (s > peaks[port]) {
+                               peaks[port] = s;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+
+/* Close down JACK when exiting */
+static void cleanup()
+{
+       const char **all_ports;
+       unsigned int i;
+
+       fprintf(stderr,"cleanup()\n");
+
+       for (i=0; i<num_meters; i++) {
+               all_ports = jack_port_get_all_connections(client, input_ports[i]);
+
+               for (i=0; all_ports && all_ports[i]; i++) {
+                       jack_disconnect(client, all_ports[i], jack_port_name(input_ports[i]));
+               }
+       }
+
+       /* Leave the jack graph */
+       jack_client_close(client);
+}
+
+
+int main(int argc, char **argv)
+{
+       int n = 0;
+       int use_ssl = 0;
+       struct libwebsocket_context *context;
+       int opts = 0;
+       char interface_name[128] = "";
+       const char *iface = NULL;
+#ifndef WIN32
+       int syslog_options = LOG_PID | LOG_PERROR;
+#endif
+       unsigned int oldus = 0;
+       struct lws_context_creation_info info;
+
+       int debug_level = 7;
+#ifndef LWS_NO_DAEMONIZE
+       int daemonize = 0;
+#endif
+
+       jack_status_t status;
+
+       memset(&info, 0, sizeof info);
+       info.port = 7681;
+
+       while (n >= 0) {
+               n = getopt_long(argc, argv, "ci:hsp:d:D", options, NULL);
+               if (n < 0)
+                       continue;
+               switch (n) {
+#ifndef LWS_NO_DAEMONIZE
+               case 'D':
+                       daemonize = 1;
+                       syslog_options &= ~LOG_PERROR;
+                       break;
+#endif
+               case 'd':
+                       debug_level = atoi(optarg);
+                       break;
+               case 's':
+                       use_ssl = 1;
+                       break;
+               case 'p':
+                       info.port = atoi(optarg);
+                       break;
+               case 'i':
+                       strncpy(interface_name, optarg, sizeof interface_name);
+                       interface_name[(sizeof interface_name) - 1] = '\0';
+                       iface = interface_name;
+                       break;
+               case 'h':
+                       fprintf(stderr, "Usage: test-server "
+                                       "[--port=<p>] [--ssl] "
+                                       "[-d <log bitfield>]\n");
+                       exit(1);
+               }
+       }
+
+#if !defined(LWS_NO_DAEMONIZE)
+       /* 
+        * normally lock path would be /var/lock/lwsts or similar, to
+        * simplify getting started without having to take care about
+        * permissions or running as root, set to /tmp/.lwsts-lock
+        */
+       if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
+               fprintf(stderr, "Failed to daemonize\n");
+               return 1;
+       }
+#endif
+
+       signal(SIGINT, sighandler);
+
+       /* we will only try to log things according to our debug_level */
+       setlogmask(LOG_UPTO (LOG_DEBUG));
+       openlog("lwsts", syslog_options, LOG_DAEMON);
+
+       /* tell the library what debug level to emit and to send it to syslog */
+       lws_set_log_level(debug_level, lwsl_emit_syslog);
+
+       lwsl_notice("libwebsockets test server - "
+                       "(C) Copyright 2010-2013 Andy Green <andy@warmcat.com> - "
+                                                   "licensed under LGPL2.1\n");
+
+       max_poll_elements = getdtablesize();
+       pollfds = malloc(max_poll_elements * sizeof (struct pollfd));
+       fd_lookup = malloc(max_poll_elements * sizeof (int));
+       if (pollfds == NULL || fd_lookup == NULL) {
+               lwsl_err("Out of memory pollfds=%d\n", max_poll_elements);
+               return -1;
+       }
+
+       info.iface = iface;
+       info.protocols = protocols;
+#ifndef LWS_NO_EXTENSIONS
+       info.extensions = libwebsocket_get_internal_extensions();
+#endif
+       if (!use_ssl) {
+               info.ssl_cert_filepath = NULL;
+               info.ssl_private_key_filepath = NULL;
+       } else {
+               /*
+               info.ssl_cert_filepath = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.pem";
+               info.ssl_private_key_filepath = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.key.pem";
+               */
+       }
+       info.gid = -1;
+       info.uid = -1;
+       info.options = opts;
+
+       // Register with Jack
+       if ((client = jack_client_open("wsmeter", JackNullOption, &status)) == 0) {
+               fprintf(stderr, "Failed to start jack client: %d\n", status);
+               exit(1);
+       }
+       fprintf(stderr,"Registering as '%s'.\n", jack_get_client_name( client ) );
+
+       // Register the cleanup function to be called when program exits
+       atexit( cleanup );
+
+       // Register the peak signal callback
+       jack_set_process_callback(client, process_peak, 0);
+
+
+       if (jack_activate(client)) {
+               fprintf(stderr, "Cannot activate client.\n");
+               exit(1);
+       }
+
+       opts = 1;
+       num_meters = 0;
+       while (argv[opts]) {
+               char in_name[255];
+               jack_port_t *port;
+               // Create our input port
+               snprintf(in_name, 255, "wsmeter_%d", num_meters);
+               if (!(input_ports[num_meters] = jack_port_register(client, in_name,
+                                               JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0))) {
+                       fprintf(stderr, "Cannot register input port 'meter'.\n");
+                       exit(1);
+               }
+
+               port = jack_port_by_name(client, argv[opts]);
+               if (port == NULL) {
+                       fprintf(stderr, "Can't find port '%s'\n", argv[opts]);
+               }
+               if (jack_connect(client, jack_port_name(port), jack_port_name(input_ports[num_meters]))) {
+                       /* failed to connect, pass */
+                       fprintf(stderr, "failed to connect\n");
+               }
+               num_meters += 1;
+               opts++;
+       }
+
+       context = libwebsocket_create_context(&info);
+       if (context == NULL) {
+               lwsl_err("libwebsocket init failed\n");
+               return -1;
+       }
+
+       n = 0;
+       while (n >= 0 && !force_exit) {
+               struct timeval tv;
+
+               gettimeofday(&tv, NULL);
+
+               read_peaks();
+
+               /*
+                * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
+                * live websocket connection using the DUMB_INCREMENT protocol,
+                * as soon as it can take more packets (usually immediately)
+                */
+
+               if (((unsigned int)tv.tv_usec - oldus) > 100000) {
+                       libwebsocket_callback_on_writable_all_protocol(&protocols[1]);
+                       oldus = tv.tv_usec;
+               }
+
+               n = poll(pollfds, count_pollfds, 25);
+               if (n < 0)
+                       continue;
+
+
+               if (n)
+                       for (n = 0; n < count_pollfds; n++)
+                               if (pollfds[n].revents)
+                                       if (libwebsocket_service_fd(context,
+                                                                 &pollfds[n]) < 0)
+                                               goto done;
+
+
+               n = libwebsocket_service(context, 25);
+       }
+
+done:
+       libwebsocket_context_destroy(context);
+
+       lwsl_notice("libwebsockets-test-server exited cleanly\n");
+
+       closelog();
+
+       return 0;
+}
diff --git a/jackwsmeter.html b/jackwsmeter.html
new file mode 100644 (file)
index 0000000..8f6b106
--- /dev/null
@@ -0,0 +1,112 @@
+<html>
+<head>
+<style>
+body {
+       margin: 0;
+}
+
+.gauge {
+       display: inline-block;
+       background: green;
+       width: 0px;
+       height: 1.2em;
+       /*
+       -webkit-transition: width .3s ease;
+       transition: width .3s ease;
+       */
+}
+
+.meter {
+       background: black;
+}
+
+#scale span {
+       position: absolute;
+       color: white;
+}
+</style>
+</head>
+<body>
+<div id="scale">
+  <span>0</span><span>-5</span><span>-10</span><span>-15</span><span>-20</span>
+  <span>-25</span><span>-30</span><span>-40</span><span>-50</span><span>-60</span>
+</div>
+<div class="meter"><span class="gauge"></span></div>
+</body>
+<script>
+function get_appropriate_ws_url()
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) == "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) == "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split('/');
+
+       return pcol + u[0];
+}
+
+
+function iec_scale(db) {
+       var def = 0.0;
+       
+       if (db < -70.0 || isNaN(db)) {
+               def = 0.0;
+       } else if (db < -60.0) {
+               def = (db + 70.0) * 0.25;
+       } else if (db < -50.0) {
+               def = (db + 60.0) * 0.5 + 2.5;
+       } else if (db < -40.0) {
+               def = (db + 50.0) * 0.75 + 7.5;
+       } else if (db < -30.0) {
+               def = (db + 40.0) * 1.5 + 15.0;
+       } else if (db < -20.0) {
+               def = (db + 30.0) * 2.0 + 30.0;
+       } else if (db < 0.0) {
+               def = (db + 20.0) * 2.5 + 50.0;
+       } else {
+               def = 100.0;
+       }
+       
+       return (def / 100.0);
+
+}
+
+scale = document.getElementById('scale');
+var i;
+for (i=0; i<scale.children.length; i++) {
+       var span = scale.children[i];
+       span.style.left = iec_scale(parseFloat(span.textContent)) * 100 + '%';
+}
+
+var ws = new WebSocket(get_appropriate_ws_url(), 'jack-wsmeter-protocol');
+ws.onmessage = function(event) {
+       var peaks = event.data.split(" ").slice(0, -1);
+       var gauges = document.getElementsByClassName('gauge');
+       var meters = document.getElementsByClassName('meter');
+       if (meters.length != peaks.length) {
+               for (i=meters.length; i>1; i--) {
+                       meters[i-1].remove();
+               }
+               for (i=1; i<peaks.length; i++) {
+                       document.body.appendChild(meters[0].cloneNode(true));
+               }
+       }
+       for (i=0; i<peaks.length; i++) {
+               gauges[i].style.width = iec_scale(parseFloat(peaks[i])) * 100 + '%';
+       }
+}
+</script>
+</html>