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
;
84 case LWS_CALLBACK_HTTP
:
85 html_filepath
= malloc(strlen(DATADIR
"/jackwsmeter.html") + 1);
86 strcpy(html_filepath
, DATADIR
"jackwsmeter.html\n\n");
87 if (access(html_filepath
, F_OK
) != 0) {
88 /* it doesn't exist in the system directory, fall back
89 * on the current directory. */
90 strcpy(html_filepath
, "jackwsmeter.html");
92 if (libwebsockets_serve_http_file(context
, wsi
, html_filepath
, "text/html")) {
94 return 1; /* through completion or error, close the socket */
100 case LWS_CALLBACK_HTTP_FILE_COMPLETION
:
103 case LWS_CALLBACK_ADD_POLL_FD
:
105 if (count_pollfds
>= max_poll_elements
) {
106 lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n");
110 fd_lookup
[fd
] = count_pollfds
;
111 pollfds
[count_pollfds
].fd
= fd
;
112 pollfds
[count_pollfds
].events
= (int)(long)len
;
113 pollfds
[count_pollfds
++].revents
= 0;
116 case LWS_CALLBACK_DEL_POLL_FD
:
117 if (!--count_pollfds
)
120 /* have the last guy take up the vacant slot */
121 pollfds
[m
] = pollfds
[count_pollfds
];
122 fd_lookup
[pollfds
[count_pollfds
].fd
] = m
;
125 case LWS_CALLBACK_SET_MODE_POLL_FD
:
126 pollfds
[fd_lookup
[fd
]].events
|= (int)(long)len
;
129 case LWS_CALLBACK_CLEAR_MODE_POLL_FD
:
130 pollfds
[fd_lookup
[fd
]].events
&= ~(int)(long)len
;
141 callback_meter(struct libwebsocket_context
*context
,
142 struct libwebsocket
*wsi
,
143 enum libwebsocket_callback_reasons reason
,
144 void *user
, void *in
, size_t len
)
150 char buf
[LWS_SEND_BUFFER_PRE_PADDING
+ 512 + LWS_SEND_BUFFER_POST_PADDING
];
151 char *p
= &buf
[LWS_SEND_BUFFER_PRE_PADDING
];
155 case LWS_CALLBACK_ESTABLISHED
:
158 case LWS_CALLBACK_SERVER_WRITEABLE
:
160 for (i
=0; i
<num_meters
; i
++) {
161 db
= 20.0f
* log10f(sent_peaks
[i
] * bias
);
162 snprintf(one_peak
, 100, "%f ", db
);
163 strcat((char*)p
, one_peak
);
166 n
= libwebsocket_write(wsi
, (unsigned char*)p
, n
, LWS_WRITE_TEXT
);
168 lwsl_err("ERROR %d writing to socket\n", n
);
180 /* list of supported protocols and callbacks */
182 static struct libwebsocket_protocols protocols
[] = {
183 /* first protocol must always be HTTP handler */
186 "http-only", /* name */
187 callback_http
, /* callback */
188 0, /* per_session_data_size */
189 0, /* max frame size / rx buffer */
192 "jack-wsmeter-protocol",
197 { NULL
, NULL
, 0, 0 } /* terminator */
200 void sighandler(int sig
)
205 static struct option options
[] = {
206 { "help", no_argument
, NULL
, 'h' },
207 { "debug", required_argument
, NULL
, 'd' },
208 { "port", required_argument
, NULL
, 'p' },
209 { "ssl-cert", required_argument
, NULL
, 'E' },
210 { "ssl-key", required_argument
, NULL
, 'k' },
211 { "interface", required_argument
, NULL
, 'i' },
212 #ifndef LWS_NO_DAEMONIZE
213 { "daemonize", no_argument
, NULL
, 'D' },
215 { "name", required_argument
, NULL
, 'n' },
220 /* Callback called by JACK when audio is available.
221 Stores value of peak sample */
222 static int process_peak(jack_nframes_t nframes
, void *arg
)
224 unsigned int i
, port
;
226 for (port
= 0; port
< num_meters
; port
++) {
227 jack_default_audio_sample_t
*in
;
229 /* just incase the port isn't registered yet */
230 if (input_ports
[port
] == 0) {
234 in
= (jack_default_audio_sample_t
*) jack_port_get_buffer(input_ports
[port
], nframes
);
236 for (i
= 0; i
< nframes
; i
++) {
237 const float s
= fabs(in
[i
]);
238 if (s
> peaks
[port
]) {
248 /* Close down JACK when exiting */
249 static void cleanup()
251 const char **all_ports
;
254 lwsl_debug("cleanup()\n");
256 for (i
=0; i
<num_meters
; i
++) {
257 all_ports
= jack_port_get_all_connections(client
, input_ports
[i
]);
259 for (j
=0; all_ports
&& all_ports
[j
]; j
++) {
260 jack_disconnect(client
, all_ports
[j
], jack_port_name(input_ports
[i
]));
264 /* Leave the jack graph */
265 jack_client_close(client
);
271 int main(int argc
, char **argv
)
274 struct libwebsocket_context
*context
;
276 char interface_name
[128] = "";
277 char jack_name
[128] = "wsmeter";
278 const char *iface
= NULL
;
279 int syslog_options
= LOG_PID
| LOG_PERROR
;
280 unsigned int oldus
= 0;
281 struct lws_context_creation_info info
;
284 #ifndef LWS_NO_DAEMONIZE
288 jack_status_t status
;
290 memset(&info
, 0, sizeof info
);
294 n
= getopt_long(argc
, argv
, "ci:hsp:d:Dn:", options
, NULL
);
298 #ifndef LWS_NO_DAEMONIZE
301 syslog_options
&= ~LOG_PERROR
;
305 debug_level
= atoi(optarg
);
308 info
.ssl_cert_filepath
= strdup(optarg
);
311 info
.ssl_private_key_filepath
= strdup(optarg
);
314 info
.port
= atoi(optarg
);
317 strncpy(interface_name
, optarg
, sizeof interface_name
);
318 interface_name
[(sizeof interface_name
) - 1] = '\0';
319 iface
= interface_name
;
322 strncpy(jack_name
, optarg
, sizeof jack_name
);
323 jack_name
[(sizeof jack_name
) - 1] = '\0';
326 fprintf(stderr
, "Usage: jackwsserver "
327 "[--port=<p>] [--ssl-cert FILEPATH] [--ssl-key FILEPATH] "
328 "[-d <log bitfield>] <port>+\n");
333 #if !defined(LWS_NO_DAEMONIZE)
335 * normally lock path would be /var/lock/jwsm or similar, to
336 * simplify getting started without having to take care about
337 * permissions or running as root, set to /tmp/.jwsm-lock
339 if (daemonize
&& lws_daemonize("/tmp/.jwsm-lock")) {
340 fprintf(stderr
, "Failed to daemonize\n");
345 signal(SIGINT
, sighandler
);
347 /* we will only try to log things according to our debug_level */
348 setlogmask(LOG_UPTO (LOG_DEBUG
));
349 openlog("jackwsmeter", syslog_options
, LOG_DAEMON
);
351 /* tell the library what debug level to emit and to send it to syslog */
352 lws_set_log_level(debug_level
, lwsl_emit_syslog
);
354 max_poll_elements
= getdtablesize();
355 pollfds
= malloc(max_poll_elements
* sizeof (struct pollfd
));
356 fd_lookup
= malloc(max_poll_elements
* sizeof (int));
357 if (pollfds
== NULL
|| fd_lookup
== NULL
) {
358 lwsl_err("Out of memory pollfds=%d\n", max_poll_elements
);
363 info
.protocols
= protocols
;
364 #ifndef LWS_NO_EXTENSIONS
365 info
.extensions
= libwebsocket_get_internal_extensions();
371 // Register with Jack
372 if ((client
= jack_client_open(jack_name
, JackNullOption
, &status
)) == 0) {
373 lwsl_err("Failed to start jack client: %d\n", status
);
376 lwsl_debug("Registering as '%s'.\n", jack_get_client_name(client
));
378 // Register the cleanup function to be called when program exits
381 // Register the peak signal callback
382 jack_set_process_callback(client
, process_peak
, 0);
385 if (jack_activate(client
)) {
386 lwsl_err("Cannot activate client.\n");
395 // Create our input port
396 snprintf(in_name
, 255, "in_%d", num_meters
);
397 if (!(input_ports
[num_meters
] = jack_port_register(client
, in_name
,
398 JACK_DEFAULT_AUDIO_TYPE
, JackPortIsInput
, 0))) {
399 lwsl_err("Cannot register input port '%s'.\n", in_name
);
403 port
= jack_port_by_name(client
, argv
[opts
]);
405 lwsl_err("Can't find port '%s'\n", argv
[opts
]);
407 if (jack_connect(client
, jack_port_name(port
), jack_port_name(input_ports
[num_meters
]))) {
408 lwsl_err("failed to connect to port '%s'\n", argv
[opts
]);
413 if (num_meters
== MAX_METERS
) {
414 lwsl_err("maximum number of meters (%d) reached.\n", MAX_METERS
);
420 if (num_meters
== 0) {
421 lwsl_err("You must specify at least one port, aborting.");
425 context
= libwebsocket_create_context(&info
);
426 if (context
== NULL
) {
427 lwsl_err("libwebsocket init failed\n");
432 while (n
>= 0 && !force_exit
) {
435 gettimeofday(&tv
, NULL
);
440 * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
441 * live websocket connection as soon as it can take more packets
442 * (usually immediately)
445 if (((unsigned int)tv
.tv_usec
- oldus
) > 100000) {
446 libwebsocket_callback_on_writable_all_protocol(&protocols
[1]);
450 n
= poll(pollfds
, count_pollfds
, 25);
456 for (n
= 0; n
< count_pollfds
; n
++)
457 if (pollfds
[n
].revents
)
458 if (libwebsocket_service_fd(context
,
463 n
= libwebsocket_service(context
, 25);
467 libwebsocket_context_destroy(context
);
469 lwsl_notice("jackwsserver exited cleanly\n");