alsa-utils/seq/aseqnet/aseqnet.c

666 lines
14 KiB
C
Raw Normal View History

/*
* network server/client for ALSA sequencer
* ver.0.1
*
* Copyright (C) 1999-2000 Takashi Iwai
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*
*/
#include "aconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <netinet/in.h>
2001-03-01 18:38:07 +01:00
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <locale.h>
#include <alsa/asoundlib.h>
#include <getopt.h>
#include <signal.h>
2007-10-25 15:39:19 +02:00
#include <assert.h>
#include "gettext.h"
/*
* prototypes
*/
static void usage(void);
static void init_buf(void);
static void init_pollfds(void);
static void close_files(void);
static void init_seq(char *source, char *dest, char *name);
static void sigterm_exit(int sig);
static void init_server(const char *port);
static void init_client(const char *server, const char *port);
static void do_loop(void);
static int copy_local_to_remote(void);
static int copy_remote_to_local(int fd);
/*
* default TCP port number
*/
#define DEFAULT_PORT "40002"
/*
* local input buffer
*/
static char *readbuf;
static int max_rdlen;
static char *writebuf;
static int cur_wrlen, max_wrlen;
#define MAX_BUF_EVENTS 200
#define MAX_CONNECTION 10
static snd_seq_t *handle;
static struct pollfd *seqifds = NULL;
static struct pollfd *seqofds = NULL;
static struct pollfd *pollfds = NULL;
static int seqifds_count = 0;
static int seqofds_count = 0;
static int pollfds_count = 0;
static int sockfd, netfd[MAX_CONNECTION] = {[0 ... MAX_CONNECTION-1] = -1};
static int max_connection;
static int cur_connected;
static int seq_port;
static int server_mode;
static int ipv6 = 0;
static int verbose = 0;
static int info = 0;
/*
* main routine
*/
static const struct option long_option[] = {
{"ipv6", 0, NULL, '6'},
{"port", 1, NULL, 'p'},
{"source", 1, NULL, 's'},
{"dest", 1, NULL, 'd'},
{"name", 1, NULL, 'n'},
{"help", 0, NULL, 'h'},
{"verbose", 0, NULL, 'v'},
{"info", 0, NULL, 'i'},
{NULL, 0, NULL, 0},
};
int main(int argc, char **argv)
{
int c;
char *port = DEFAULT_PORT;
char *source = NULL, *dest = NULL;
char *name = NULL;
#ifdef ENABLE_NLS
setlocale(LC_ALL, "");
textdomain(PACKAGE);
#endif
while ((c = getopt_long(argc, argv, "p:s:d:n:6hvi", long_option, NULL)) != -1) {
switch (c) {
case '6':
ipv6 = 1;
break;
case 'p':
port = optarg;
break;
case 's':
source = optarg;
break;
case 'd':
dest = optarg;
break;
case 'n':
name = optarg;
break;
case 'v':
verbose++;
break;
case 'i':
info++;
break;
default:
usage();
exit(1);
}
}
signal(SIGINT, sigterm_exit);
signal(SIGTERM, sigterm_exit);
init_buf();
init_seq(source, dest, name);
if (optind >= argc) {
server_mode = 1;
max_connection = MAX_CONNECTION;
init_pollfds();
init_server(port);
} else {
server_mode = 0;
max_connection = 1;
init_pollfds();
init_client(argv[optind], port);
}
do_loop();
close_files();
return 0;
}
/*
* print usage
*/
static void usage(void)
{
printf(_("aseqnet - network client/server on ALSA sequencer\n"));
printf(_(" Copyright (C) 1999 Takashi Iwai\n"));
printf(_("usage:\n"));
printf(_(" server mode: aseqnet [-options]\n"));
printf(_(" client mode: aseqnet [-options] server_host\n"));
printf(_("options:\n"));
printf(_(" -6,--ipv6 : use IPv6 TCP protocol\n"));
printf(_(" -p,--port # : specify TCP port (digit or service name)\n"));
printf(_(" -s,--source addr : read from given addr (client:port)\n"));
printf(_(" -d,--dest addr : write to given addr (client:port)\n"));
printf(_(" -n,--name value : use a specific midi process name\n"));
printf(_(" -v, --verbose : print verbose messages\n"));
printf(_(" -i, --info : print certain received events\n"));
}
/*
* allocate and initialize buffers
*/
static void init_buf(void)
{
max_wrlen = MAX_BUF_EVENTS * sizeof(snd_seq_event_t);
max_rdlen = MAX_BUF_EVENTS * sizeof(snd_seq_event_t);
writebuf = malloc(max_wrlen);
readbuf = malloc(max_rdlen);
if (writebuf == NULL || readbuf == NULL) {
fprintf(stderr, _("can't malloc\n"));
exit(1);
}
memset(writebuf, 0, max_wrlen);
memset(readbuf, 0, max_rdlen);
cur_wrlen = 0;
}
/*
* allocate and initialize poll array
*/
static void init_pollfds(void)
{
pollfds_count = seqifds_count + seqofds_count + 1 + max_connection;
pollfds = (struct pollfd *)calloc(pollfds_count, sizeof(struct pollfd));
assert(pollfds);
}
/*
* close all files
*/
static void close_files(void)
{
int i;
if (verbose)
fprintf(stderr, _("closing files..\n"));
for (i = 0; i < max_connection; i++) {
if (netfd[i] >= 0)
close(netfd[i]);
}
if (sockfd >= 0)
close(sockfd);
}
/*
* initialize sequencer
*/
static void init_seq(char *source, char *dest, char* name)
{
snd_seq_addr_t addr;
int err, counti, counto;
if (snd_seq_open(&handle, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
perror("snd_seq_open");
exit(1);
}
if (seqifds)
free(seqifds);
if (seqofds)
free(seqofds);
counti = seqifds_count = snd_seq_poll_descriptors_count(handle, POLLIN);
assert(counti > 0);
counto = seqofds_count = snd_seq_poll_descriptors_count(handle, POLLOUT);
assert(counto > 0);
seqifds = (struct pollfd *)calloc(counti, sizeof(struct pollfd));
assert(seqifds);
seqofds = (struct pollfd *)calloc(counto, sizeof(struct pollfd));
assert(seqofds);
err = snd_seq_poll_descriptors(handle, seqifds, counti, POLLIN);
assert(err == counti);
err = snd_seq_poll_descriptors(handle, seqofds, counto, POLLOUT);
assert(err == counto);
snd_seq_nonblock(handle, 1);
/* set client info */
if (name)
snd_seq_set_client_name(handle, name);
else {
if (server_mode)
snd_seq_set_client_name(handle, "Net Server");
else
snd_seq_set_client_name(handle, "Net Client");
}
/* create a port */
seq_port = snd_seq_create_simple_port(handle, "Network",
SND_SEQ_PORT_CAP_READ |
SND_SEQ_PORT_CAP_WRITE |
SND_SEQ_PORT_CAP_SUBS_READ |
SND_SEQ_PORT_CAP_SUBS_WRITE,
SND_SEQ_PORT_TYPE_MIDI_GENERIC);
if (seq_port < 0) {
perror("create seq port");
exit(1);
}
if (verbose)
fprintf(stderr, _("sequencer opened: %d:%d\n"),
snd_seq_client_id(handle), seq_port);
/* explicit subscriptions */
if (source) {
/* read subscription */
if (snd_seq_parse_address(handle, &addr, source) < 0) {
fprintf(stderr, _("invalid source address %s\n"), source);
exit(1);
}
if (snd_seq_connect_from(handle, seq_port, addr.client, addr.port)) {
perror("read subscription");
exit(1);
}
}
if (dest) {
/* write subscription */
if (snd_seq_parse_address(handle, &addr, dest) < 0) {
fprintf(stderr, _("invalid destination address %s\n"), dest);
exit(1);
}
if (snd_seq_connect_to(handle, seq_port, addr.client, addr.port)) {
perror("write subscription");
exit(1);
}
}
}
/*
* translate the binary network address to ASCII
*/
static void get_net_addr(struct addrinfo *rp, char *buf, size_t buflen)
{
void *ptr;
switch (rp->ai_family) {
case AF_INET:
ptr = &((struct sockaddr_in *) rp->ai_addr)->sin_addr;
break;
case AF_INET6:
ptr = &((struct sockaddr_in6 *) rp->ai_addr)->sin6_addr;
break;
default:
ptr = NULL;
}
buf[buflen-1] = '\0';
inet_ntop(rp->ai_family, ptr, buf, buflen-1);
}
/*
* signal handler
*/
static void sigterm_exit(int)
{
close_files();
exit(1);
}
/*
* initialize network server
*/
static void init_server(const char *port)
{
struct addrinfo hints;
struct addrinfo *result, *rp;
char buf[100];
int i;
int curstate = 1;
int save_errno = 0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = ipv6 ? AF_INET6 : AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if (getaddrinfo(NULL, port, &hints, &result) < 0) {
fprintf(stderr, _("can't get address\n"));
exit(1);
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
if ((sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) < 0){
perror("create socket");
exit(1);
}
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &curstate, sizeof(curstate)) < 0) {
perror("setsockopt");
exit(1);
}
if (verbose) {
get_net_addr(rp, buf, sizeof(buf));
fprintf(stderr, _("connecting to: %s\n"), buf);
}
if (bind(sockfd, rp->ai_addr, rp->ai_addrlen) == 0)
break;
save_errno = errno;
close(sockfd);
}
if (rp == NULL) {
errno = save_errno;
perror("bind");
exit(1);
}
freeaddrinfo(result);
if (listen(sockfd, 5) < 0) {
perror("can't listen");
exit(1);
}
cur_connected = 0;
for (i = 0; i < max_connection; i++)
netfd[i] = -1;
}
/*
* start connection on server
*/
static void start_connection(void)
{
struct sockaddr_in addr;
int i;
socklen_t addr_len;
for (i = 0; i < max_connection; i++) {
if (netfd[i] < 0)
break;
}
if (i >= max_connection) {
fprintf(stderr, _("too many connections!\n"));
exit(1);
}
memset(&addr, 0, sizeof(addr));
addr_len = sizeof(addr);
netfd[i] = accept(sockfd, (struct sockaddr *)&addr, &addr_len);
if (netfd[i] < 0) {
perror("accept");
exit(1);
}
if (verbose)
fprintf(stderr, _("accepted[%d]\n"), netfd[i]);
cur_connected++;
}
/*
* initialize network client
*/
static void init_client(const char *server, const char *port)
{
struct addrinfo hints;
struct addrinfo *result, *rp;
char buf[100];
int curstate = 1;
int fd;
int save_errno = 0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if (getaddrinfo(server, port, &hints, &result) < 0) {
fprintf(stderr, _("can't get address %s\n"), server);
exit(1);
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
if ((fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) < 0){
perror("create socket");
exit(1);
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &curstate, sizeof(curstate)) < 0) {
perror("setsockopt");
exit(1);
}
if (verbose) {
get_net_addr(rp, buf, sizeof(buf));
fprintf(stderr, _("connecting to: %s\n"), buf);
}
if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0)
break;
save_errno = errno;
close(fd);
}
if (rp == NULL) {
errno = save_errno;
perror("connect");
exit(1);
}
freeaddrinfo(result);
if (verbose)
fprintf(stderr, _("ok.. connected\n"));
netfd[0] = fd;
cur_connected = 1;
}
/*
* event loop
*/
static void do_loop(void)
{
int i, rc, width;
int seqifd_ptr, sockfd_ptr = -1, netfd_ptr;
for (;;) {
memset(pollfds, 0, pollfds_count * sizeof(struct pollfd));
seqifd_ptr = 0;
memcpy(pollfds, seqifds, sizeof(*seqifds)*(width = seqifds_count));
if (server_mode) {
sockfd_ptr = width;
pollfds[width].fd = sockfd;
pollfds[width].events = POLLIN;
width++;
}
netfd_ptr = width;
for (i = 0; i < max_connection; i++) {
if (netfd[i] >= 0) {
pollfds[width].fd = netfd[i];
pollfds[width].events = POLLIN;
width++;
}
}
do {
rc = poll(pollfds, width, -1);
} while (rc <= 0 && errno == EINTR);
if (rc <= 0) {
perror("poll");
exit(1);
}
if (server_mode) {
if (pollfds[sockfd_ptr].revents & (POLLIN|POLLOUT))
start_connection();
}
for (i = 0; i < seqifds_count; i++)
if (pollfds[seqifd_ptr + i].revents & (POLLIN|POLLOUT)) {
if (copy_local_to_remote())
return;
break;
}
for (i = 0; i < max_connection; i++) {
if (netfd[i] < 0)
continue;
if (pollfds[netfd_ptr + i].revents & (POLLIN|POLLOUT)) {
if (copy_remote_to_local(netfd[i])) {
netfd[i] = -1;
cur_connected--;
if (cur_connected <= 0)
return;
}
}
}
}
}
/*
* flush write buffer - send data to the socket
*/
static void flush_writebuf(void)
{
if (cur_wrlen) {
int i;
for (i = 0; i < max_connection; i++) {
if (netfd[i] >= 0) {
ssize_t wrlen = write(netfd[i], writebuf, cur_wrlen);
if (wrlen != (ssize_t)cur_wrlen)
fprintf(stderr, "write error: %s", wrlen < 0 ? strerror(errno) : "short");
}
}
cur_wrlen = 0;
}
}
/*
* get space from write buffer
*/
static char *get_writebuf(int len)
{
char *buf;
if (cur_wrlen + len >= max_wrlen)
flush_writebuf();
buf = writebuf + cur_wrlen;
cur_wrlen += len;
return buf;
}
static void print_event(snd_seq_event_t *ev)
{
switch (ev->type) {
case SND_SEQ_EVENT_CONTROLLER:
printf(_("Channel %2d: Control event : %5d\n"),
ev->data.control.channel, ev->data.control.value);
break;
case SND_SEQ_EVENT_PITCHBEND:
printf(_("Channel %2d: Pitchbender : %5d\n"),
ev->data.control.channel, ev->data.control.value);
break;
case SND_SEQ_EVENT_NOTEON:
printf(_("Channel %2d: Note On event : %5d\n"),
ev->data.control.channel, ev->data.note.note);
break;
case SND_SEQ_EVENT_NOTEOFF:
printf(_("Channel %2d: Note Off event: %5d\n"),
ev->data.control.channel, ev->data.note.note);
break;
}
}
#define EVENT_PACKET_SIZE 32
/*
* copy events from sequencer to port(s)
*/
static int copy_local_to_remote(void)
{
int rc;
snd_seq_event_t *ev;
char *buf;
while ((rc = snd_seq_event_input(handle, &ev)) >= 0 && ev) {
if (ev->type >= SND_SEQ_EVENT_CLIENT_START &&
! snd_seq_ev_is_variable_type(ev)) {
snd_seq_free_event(ev);
continue;
}
if (snd_seq_ev_is_variable(ev)) {
int len;
len = EVENT_PACKET_SIZE + ev->data.ext.len;
buf = get_writebuf(len);
memcpy(buf, ev, sizeof(snd_seq_event_t));
memcpy(buf + EVENT_PACKET_SIZE, ev->data.ext.ptr, ev->data.ext.len);
} else {
buf = get_writebuf(EVENT_PACKET_SIZE);
memcpy(buf, ev, EVENT_PACKET_SIZE);
}
if (info)
print_event(ev);
snd_seq_free_event(ev);
}
flush_writebuf();
return 0;
}
/*
* copy events from a port to sequencer
*/
static int copy_remote_to_local(int fd)
{
int count;
char *buf;
snd_seq_event_t *ev;
count = read(fd, readbuf, MAX_BUF_EVENTS * sizeof(snd_seq_event_t));
buf = readbuf;
if (count == 0) {
if (verbose)
fprintf(stderr, _("disconnected\n"));
return 1;
}
while (count > 0) {
ev = (snd_seq_event_t*)buf;
buf += EVENT_PACKET_SIZE;
count -= EVENT_PACKET_SIZE;
if (snd_seq_ev_is_variable(ev) && ev->data.ext.len > 0) {
ev->data.ext.ptr = buf;
buf += ev->data.ext.len;
count -= ev->data.ext.len;
}
snd_seq_ev_set_direct(ev);
snd_seq_ev_set_source(ev, seq_port);
snd_seq_ev_set_subs(ev);
if (info)
print_event(ev);
snd_seq_event_output(handle, ev);
}
snd_seq_drain_output(handle);
return 0;
}