mirror of
https://github.com/alsa-project/alsa-lib.git
synced 2025-10-29 05:40:25 -04:00
Added aseqnet...
This commit is contained in:
parent
278b684054
commit
33348ba6f4
3 changed files with 559 additions and 1 deletions
|
|
@ -1,5 +1,5 @@
|
|||
check_PROGRAMS=control mixer switches pause pcm latency seq \
|
||||
playmidi1 timer loopback aconnect
|
||||
playmidi1 timer loopback aconnect aseqnet
|
||||
|
||||
control_LDADD=../src/libasound.la
|
||||
mixer_LDADD=../src/libasound.la
|
||||
|
|
@ -12,6 +12,7 @@ playmidi1_LDADD=../src/libasound.la
|
|||
timer_LDADD=../src/libasound.la
|
||||
loopback_LDADD=../src/libasound.la
|
||||
aconnect_LDADD=../src/libasound.la
|
||||
aseqnet_LDADD=../src/libasound.la
|
||||
|
||||
INCLUDES=-I$(top_srcdir)/include
|
||||
CFLAGS=-static -Wall -pipe -g
|
||||
|
|
|
|||
52
test/README.aseqnet
Normal file
52
test/README.aseqnet
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
================================================================
|
||||
ALSA sequencer connectors
|
||||
ver.0.1
|
||||
Copyright (C) 1999 Takashi Iwai
|
||||
================================================================
|
||||
|
||||
* ASEQNET
|
||||
|
||||
aseqnet is a sequencer client which sends/receives events over
|
||||
network. Suppose two hosts (hostA and hostB) connected by network.
|
||||
You need to run ALSA system on both hosts. Then, start aseqnet as a
|
||||
server on hostA:
|
||||
|
||||
hostA% aseqnet
|
||||
sequencer opened: 128:0
|
||||
|
||||
A user client 128 with port 0 was opened. (The client number may
|
||||
vary.) At next, start client on hostB. The argument is the hostname
|
||||
where server is running.
|
||||
|
||||
hostB% aseqnet hostA
|
||||
sequencer opened: 132:0
|
||||
|
||||
Now events sent to hostA:128:0 is transferred to hostB:132:0, and vice
|
||||
versa.
|
||||
|
||||
You can connect these ports arbitrary to other sequencer ports.
|
||||
For example, connect hostB:132:0 to a MIDI output device 65:0. The
|
||||
aconnect utility can be used for this:
|
||||
|
||||
hostB% aconnect 132:0 65:0
|
||||
|
||||
Events to hostA:128:0 will be delivered indirectly to hostB:65:0.
|
||||
You'll hear MIDI sounds as following:
|
||||
|
||||
hostA% pmidi -p 128:0 foo.mid
|
||||
|
||||
The multiple clients may exist simultaneously. If hostC is connected
|
||||
as a client to hostA, events from from hostA are sent to all connected
|
||||
network clients, hostB and hostC. However, only one connection is
|
||||
allowed from a client to a server.
|
||||
|
||||
To disconnect network, stop all clients before server by ctrl-C or
|
||||
sending signal to them. The server will automatically quit.
|
||||
|
||||
The available options are:
|
||||
|
||||
-p port : specify the TCP port number or TCP service name.
|
||||
Default value is 9009 (I don't know it's allowed..)
|
||||
-s addr : explicit read-subscription to the given address
|
||||
(client:addr).
|
||||
-d addr : explicit write-subscription to the given address.
|
||||
505
test/aseqnet.c
Normal file
505
test/aseqnet.c
Normal file
|
|
@ -0,0 +1,505 @@
|
|||
/*
|
||||
* network server/client for ALSA sequencer
|
||||
* ver.0.1
|
||||
*
|
||||
* Copyright (C) 1999 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/asoundlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static void usage(void);
|
||||
static void init_buf(void);
|
||||
static void init_seq(char *source, char *dest);
|
||||
static int get_port(char *service);
|
||||
static void init_server(int port);
|
||||
static void init_client(char *server, int 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 9009
|
||||
|
||||
/*
|
||||
* 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 int seqfd, sockfd, netfd[MAX_CONNECTION] = {[1 ... MAX_CONNECTION] = -1};
|
||||
static int max_connection;
|
||||
static int cur_connected;
|
||||
static int seq_port;
|
||||
|
||||
static int server_mode;
|
||||
|
||||
|
||||
/*
|
||||
* main routine
|
||||
*/
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int c, i;
|
||||
int port = DEFAULT_PORT;
|
||||
char *source = NULL, *dest = NULL;
|
||||
|
||||
while ((c = getopt(argc, argv, "p:s:d:")) != -1) {
|
||||
switch (c) {
|
||||
case 'p':
|
||||
if (isdigit(*optarg))
|
||||
port = atoi(optarg);
|
||||
else
|
||||
port = get_port(optarg);
|
||||
break;
|
||||
case 's':
|
||||
source = optarg;
|
||||
break;
|
||||
case 'd':
|
||||
dest = optarg;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
init_buf();
|
||||
init_seq(source, dest);
|
||||
|
||||
if (optind >= argc) {
|
||||
server_mode = 1;
|
||||
max_connection = MAX_CONNECTION;
|
||||
init_server(port);
|
||||
} else {
|
||||
server_mode = 0;
|
||||
max_connection = 1;
|
||||
init_client(argv[optind], port);
|
||||
}
|
||||
|
||||
do_loop();
|
||||
|
||||
for (i = 0; i < max_connection; i++) {
|
||||
if (netfd[i] >= 0)
|
||||
close(netfd[i]);
|
||||
}
|
||||
if (sockfd >= 0)
|
||||
close(sockfd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* print usage
|
||||
*/
|
||||
static void usage(void)
|
||||
{
|
||||
fprintf(stderr, "aseqnet - network client/server on ALSA sequencer\n");
|
||||
fprintf(stderr, " Copyright (C) 1999 Takashi Iwai\n");
|
||||
fprintf(stderr, "usage:\n");
|
||||
fprintf(stderr, " server mode: aseqnet [-options]\n");
|
||||
fprintf(stderr, " client mode: aseqnet [-options] server_host\n");
|
||||
fprintf(stderr, "options:\n");
|
||||
fprintf(stderr, " -p port : sepcify TCP port (digit or service name)\n");
|
||||
fprintf(stderr, " -s addr : read from given addr (client:port)\n");
|
||||
fprintf(stderr, " -d addr : write to given addr (client:port)\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;
|
||||
}
|
||||
|
||||
/*
|
||||
* parse client:port argument
|
||||
*/
|
||||
static void parse_addr(snd_seq_addr_t *addr, char *arg)
|
||||
{
|
||||
char *p;
|
||||
|
||||
addr->client = atoi(arg);
|
||||
if ((p = strchr(arg, ':')) != NULL)
|
||||
addr->port = atoi(p + 1);
|
||||
else
|
||||
addr->port = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize sequencer
|
||||
*/
|
||||
static void init_seq(char *source, char *dest)
|
||||
{
|
||||
snd_seq_client_info_t cinfo;
|
||||
snd_seq_port_info_t pinfo;
|
||||
snd_seq_port_subscribe_t subs;
|
||||
|
||||
if (snd_seq_open(&handle, SND_SEQ_OPEN) < 0) {
|
||||
perror("snd_seq_open");
|
||||
exit(1);
|
||||
}
|
||||
seqfd = snd_seq_file_descriptor(handle);
|
||||
snd_seq_block_mode(handle, 0);
|
||||
|
||||
/* set client info */
|
||||
memset(&cinfo, 0, sizeof(cinfo));
|
||||
if (server_mode)
|
||||
strcpy(cinfo.name, "Net Server");
|
||||
else
|
||||
strcpy(cinfo.name, "Net Client");
|
||||
if (snd_seq_set_client_info(handle, &cinfo) < 0) {
|
||||
perror("set client info");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* create a port */
|
||||
memset(&pinfo, 0, sizeof(pinfo));
|
||||
pinfo.capability = SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_WRITE|
|
||||
SND_SEQ_PORT_CAP_SUBS_READ|SND_SEQ_PORT_CAP_SUBS_WRITE;
|
||||
strcpy(pinfo.name, "Network");
|
||||
pinfo.port = 0;
|
||||
if (snd_seq_create_port(handle, &pinfo) < 0) {
|
||||
perror("create seq port");
|
||||
exit(1);
|
||||
}
|
||||
seq_port = pinfo.port;
|
||||
fprintf(stderr, "sequencer opened: %d:%d\n", pinfo.client, pinfo.port);
|
||||
|
||||
/* explicit subscriptions */
|
||||
memset(&subs, 0, sizeof(subs));
|
||||
if (source) {
|
||||
/* read subscription */
|
||||
parse_addr(&subs.sender, source);
|
||||
subs.dest.client = pinfo.client;
|
||||
subs.dest.port = pinfo.port;
|
||||
subs.sender.queue = subs.dest.queue = 0;
|
||||
if (snd_seq_subscribe_port(handle, &subs)) {
|
||||
perror("read subscription");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (dest) {
|
||||
/* write subscription */
|
||||
parse_addr(&subs.dest, dest);
|
||||
subs.sender.client = pinfo.client;
|
||||
subs.sender.port = pinfo.port;
|
||||
subs.sender.queue = subs.dest.queue = 0;
|
||||
if (snd_seq_subscribe_port(handle, &subs)) {
|
||||
perror("write subscription");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* convert from string to TCP port number
|
||||
*/
|
||||
static int get_port(char *service)
|
||||
{
|
||||
struct servent *sp;
|
||||
|
||||
if ((sp = getservbyname(service, "tcp")) == NULL){
|
||||
fprintf(stderr, "%s is not found in /etc/services\n", service);
|
||||
return -1;
|
||||
}
|
||||
return sp->s_port;
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize network server
|
||||
*/
|
||||
static void init_server(int port)
|
||||
{
|
||||
int i;
|
||||
struct sockaddr_in addr;
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sockfd < 0) {
|
||||
fprintf(stderr, "can't create a socket\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (bind(sockfd, &addr, sizeof(addr)) < 0) {
|
||||
fprintf(stderr, "can't bind address\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (listen(sockfd, 5) < 0) {
|
||||
fprintf(stderr, "can't listen on socket\n");
|
||||
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;
|
||||
int 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) {
|
||||
fprintf(stderr, "can't accept\n");
|
||||
exit(1);
|
||||
}
|
||||
fprintf(stderr, "accepted[%d]\n", netfd[i]);
|
||||
cur_connected++;
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize network client
|
||||
*/
|
||||
static void init_client(char *server, int port)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
struct hostent *host;
|
||||
int fd;
|
||||
|
||||
if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){
|
||||
fprintf(stderr, "can't create socket\n");
|
||||
exit(1);
|
||||
}
|
||||
if ((host = gethostbyname(server)) == NULL){
|
||||
fprintf(stderr,"can't get address %s\n", server);
|
||||
exit(1);
|
||||
}
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_family = AF_INET;
|
||||
memcpy(&addr.sin_addr, host->h_addr, host->h_length);
|
||||
if (connect(fd, &addr, sizeof(addr)) < 0) {
|
||||
fprintf(stderr,"can't connect\n");
|
||||
exit(1);
|
||||
}
|
||||
fprintf(stderr, "ok.. connected\n");
|
||||
netfd[0] = fd;
|
||||
cur_connected = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* set file descriptor
|
||||
*/
|
||||
static void set_fd(int fd, fd_set *p, int *width)
|
||||
{
|
||||
FD_SET(fd, p);
|
||||
if (fd >= *width)
|
||||
*width = fd + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* event loop
|
||||
*/
|
||||
static void do_loop(void)
|
||||
{
|
||||
fd_set rfd;
|
||||
int i, rc, width;
|
||||
|
||||
for (;;) {
|
||||
FD_ZERO(&rfd);
|
||||
width = 0;
|
||||
set_fd(seqfd, &rfd, &width);
|
||||
if (server_mode)
|
||||
set_fd(sockfd, &rfd, &width);
|
||||
for (i = 0; i < max_connection; i++) {
|
||||
if (netfd[i] >= 0)
|
||||
set_fd(netfd[i], &rfd, &width);
|
||||
}
|
||||
rc = select(width, &rfd, NULL, NULL, NULL);
|
||||
if (rc <= 0)
|
||||
exit(1);
|
||||
if (server_mode) {
|
||||
if (FD_ISSET(sockfd, &rfd))
|
||||
start_connection();
|
||||
}
|
||||
if (FD_ISSET(seqfd, &rfd)) {
|
||||
if (copy_local_to_remote())
|
||||
break;
|
||||
}
|
||||
for (i = 0; i < max_connection; i++) {
|
||||
if (netfd[i] < 0)
|
||||
continue;
|
||||
if (FD_ISSET(netfd[i], &rfd)) {
|
||||
if (copy_remote_to_local(netfd[i])) {
|
||||
netfd[i] = -1;
|
||||
cur_connected--;
|
||||
if (cur_connected <= 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* is variable length event?
|
||||
*/
|
||||
#define is_varlen(ev) (((ev)->flags & SND_SEQ_EVENT_LENGTH_MASK) == SND_SEQ_EVENT_LENGTH_VARIABLE)
|
||||
|
||||
/*
|
||||
* 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)
|
||||
write(netfd[i], writebuf, cur_wrlen);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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_free_event(ev);
|
||||
continue;
|
||||
}
|
||||
if (is_varlen(ev)) {
|
||||
int len;
|
||||
len = sizeof(snd_seq_event_t) + ev->data.ext.len;
|
||||
buf = get_writebuf(len);
|
||||
memcpy(buf, ev, sizeof(snd_seq_event_t));
|
||||
memcpy(buf + sizeof(snd_seq_event_t), ev->data.ext.ptr, ev->data.ext.len);
|
||||
} else {
|
||||
buf = get_writebuf(sizeof(snd_seq_event_t));
|
||||
memcpy(buf, ev, sizeof(snd_seq_event_t));
|
||||
}
|
||||
}
|
||||
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) {
|
||||
fprintf(stderr, "disconnected\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (count > 0) {
|
||||
ev = snd_seq_create_event();
|
||||
if (ev == NULL) {
|
||||
fprintf(stderr, "can't malloc\n");
|
||||
exit(1);
|
||||
}
|
||||
memcpy(ev, buf, sizeof(snd_seq_event_t));
|
||||
buf += sizeof(snd_seq_event_t);
|
||||
count -= sizeof(snd_seq_event_t);
|
||||
if (is_varlen(ev) && ev->data.ext.len > 0) {
|
||||
ev->data.ext.ptr = malloc(ev->data.ext.len);
|
||||
if (ev->data.ext.ptr == NULL) {
|
||||
fprintf(stderr, "can't malloc\n");
|
||||
exit(1);
|
||||
}
|
||||
memcpy(ev->data.ext.ptr, buf, ev->data.ext.len);
|
||||
buf += ev->data.ext.len;
|
||||
count -= ev->data.ext.len;
|
||||
}
|
||||
ev->source.port = seq_port;
|
||||
ev->dest.queue = SND_SEQ_ADDRESS_SUBSCRIBERS;
|
||||
snd_seq_event_output(handle, ev);
|
||||
snd_seq_free_event(ev);
|
||||
}
|
||||
|
||||
snd_seq_flush_output(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue