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)
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];
145 case LWS_CALLBACK_ESTABLISHED:
148 case LWS_CALLBACK_SERVER_WRITEABLE:
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);
156 n = libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
158 lwsl_err("ERROR %d writing to socket\n", n);
170 /* list of supported protocols and callbacks */
172 static struct libwebsocket_protocols protocols[] = {
173 /* first protocol must always be HTTP handler */
176 "http-only", /* name */
177 callback_http, /* callback */
178 0, /* per_session_data_size */
179 0, /* max frame size / rx buffer */
182 "jack-wsmeter-protocol",
187 { NULL, NULL, 0, 0 } /* terminator */
190 void sighandler(int sig)
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' },
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 jack_default_audio_sample_t *in;
214 unsigned int i, port;
216 for (port = 0; port < num_meters; port++) {
217 jack_default_audio_sample_t *in, *out;
219 /* just incase the port isn't registered yet */
220 if (input_ports[port] == 0) {
224 in = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[port], nframes);
226 for (i = 0; i < nframes; i++) {
227 const float s = fabs(in[i]);
228 if (s > peaks[port]) {
238 /* Close down JACK when exiting */
239 static void cleanup()
241 const char **all_ports;
244 fprintf(stderr,"cleanup()\n");
246 for (i=0; i<num_meters; i++) {
247 all_ports = jack_port_get_all_connections(client, input_ports[i]);
249 for (i=0; all_ports && all_ports[i]; i++) {
250 jack_disconnect(client, all_ports[i], jack_port_name(input_ports[i]));
254 /* Leave the jack graph */
255 jack_client_close(client);
259 int main(int argc, char **argv)
263 struct libwebsocket_context *context;
265 char interface_name[128] = "";
266 const char *iface = NULL;
268 int syslog_options = LOG_PID | LOG_PERROR;
270 unsigned int oldus = 0;
271 struct lws_context_creation_info info;
274 #ifndef LWS_NO_DAEMONIZE
278 jack_status_t status;
280 memset(&info, 0, sizeof info);
284 n = getopt_long(argc, argv, "ci:hsp:d:D", options, NULL);
288 #ifndef LWS_NO_DAEMONIZE
291 syslog_options &= ~LOG_PERROR;
295 debug_level = atoi(optarg);
301 info.port = atoi(optarg);
304 strncpy(interface_name, optarg, sizeof interface_name);
305 interface_name[(sizeof interface_name) - 1] = '\0';
306 iface = interface_name;
309 fprintf(stderr, "Usage: test-server "
310 "[--port=<p>] [--ssl] "
311 "[-d <log bitfield>]\n");
316 #if !defined(LWS_NO_DAEMONIZE)
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
322 if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
323 fprintf(stderr, "Failed to daemonize\n");
328 signal(SIGINT, sighandler);
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);
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);
337 lwsl_notice("libwebsockets test server - "
338 "(C) Copyright 2010-2013 Andy Green <andy@warmcat.com> - "
339 "licensed under LGPL2.1\n");
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);
350 info.protocols = protocols;
351 #ifndef LWS_NO_EXTENSIONS
352 info.extensions = libwebsocket_get_internal_extensions();
355 info.ssl_cert_filepath = NULL;
356 info.ssl_private_key_filepath = NULL;
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";
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);
372 fprintf(stderr,"Registering as '%s'.\n", jack_get_client_name( client ) );
374 // Register the cleanup function to be called when program exits
377 // Register the peak signal callback
378 jack_set_process_callback(client, process_peak, 0);
381 if (jack_activate(client)) {
382 fprintf(stderr, "Cannot activate client.\n");
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");
399 port = jack_port_by_name(client, argv[opts]);
401 fprintf(stderr, "Can't find port '%s'\n", argv[opts]);
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");
411 context = libwebsocket_create_context(&info);
412 if (context == NULL) {
413 lwsl_err("libwebsocket init failed\n");
418 while (n >= 0 && !force_exit) {
421 gettimeofday(&tv, NULL);
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)
431 if (((unsigned int)tv.tv_usec - oldus) > 100000) {
432 libwebsocket_callback_on_writable_all_protocol(&protocols[1]);
436 n = poll(pollfds, count_pollfds, 25);
442 for (n = 0; n < count_pollfds; n++)
443 if (pollfds[n].revents)
444 if (libwebsocket_service_fd(context,
449 n = libwebsocket_service(context, 25);
453 libwebsocket_context_destroy(context);
455 lwsl_notice("libwebsockets-test-server exited cleanly\n");