From 010723cb02b26c608537fc537d5e963ff6402f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Tue, 23 Mar 2010 11:54:21 +0000 Subject: forked mrpeach's "net" svn path=/trunk/externals/iem/iemnet/; revision=13240 --- tcpclient-help.pd | 108 ++++++ tcpclient.c | 552 +++++++++++++++++++++++++++ tcpreceive-help.pd | 22 ++ tcpreceive.c | 333 ++++++++++++++++ tcpsend-help.pd | 31 ++ tcpsend.c | 254 +++++++++++++ tcpserver-help.pd | 181 +++++++++ tcpserver.c | 1065 ++++++++++++++++++++++++++++++++++++++++++++++++++++ test.txt | 1 + udpreceive-help.pd | 19 + udpreceive.c | 160 ++++++++ udpreceive~.c | 805 +++++++++++++++++++++++++++++++++++++++ udpsend-help.pd | 31 ++ udpsend.c | 257 +++++++++++++ udpsend~-help.pd | 173 +++++++++ udpsend~.c | 701 ++++++++++++++++++++++++++++++++++ udpsend~.h | 102 +++++ 17 files changed, 4795 insertions(+) create mode 100644 tcpclient-help.pd create mode 100644 tcpclient.c create mode 100644 tcpreceive-help.pd create mode 100644 tcpreceive.c create mode 100644 tcpsend-help.pd create mode 100644 tcpsend.c create mode 100644 tcpserver-help.pd create mode 100644 tcpserver.c create mode 100644 test.txt create mode 100644 udpreceive-help.pd create mode 100644 udpreceive.c create mode 100644 udpreceive~.c create mode 100644 udpsend-help.pd create mode 100644 udpsend.c create mode 100644 udpsend~-help.pd create mode 100644 udpsend~.c create mode 100644 udpsend~.h diff --git a/tcpclient-help.pd b/tcpclient-help.pd new file mode 100644 index 0000000..1803d01 --- /dev/null +++ b/tcpclient-help.pd @@ -0,0 +1,108 @@ +#N canvas 184 70 1096 649 12; +#X msg -112 56 disconnect; +#X obj 219 439 unpack 0 0 0 0; +#X floatatom 219 462 3 0 0 0 - - -; +#X floatatom 250 462 3 0 0 0 - - -; +#X floatatom 282 462 3 0 0 0 - - -; +#X floatatom 314 462 3 0 0 0 - - -; +#X text 179 461 from; +#X msg -175 -7 connect 132.205.142.12 80; +#X obj 199 387 tcpclient; +#X obj 239 413 tgl 15 0 empty empty connected 18 7 0 8 -24198 -241291 +-1 0 1; +#X msg -88 80 dump \$1; +#X obj -140 65 tgl 15 0 empty empty empty 0 -6 0 8 -4034 -257985 -1 +1 1; +#X msg 11 179 receive; +#X msg 35 203 recv; +#X text -59 -58 connect with an IP address and port number; +#X msg -200 -32 connect www.concordia.ca 80; +#X text -17 79 print received messages to main window in hexdump format +; +#X text 270 386 tcpclient opens a tcp socket to send and receive bytes +on; +#X text -217 305 See also:; +#X obj -212 329 netclient; +#X msg -225 -57 connect 127.0.0.1 9997; +#X obj -212 352 tcpreceive; +#X text -214 374 can receive messages from tcpclient; +#X text -136 328 is what tcpclient is based on; +#X text 250 513 Received messages are output as a list of bytes; +#X text 77 176 get any received data (not useful unless you need it +faster than once per 20ms); +#X text 220 225 semicolon-terminated string for netserver or netreceive +; +#X msg 59 227 send 49 127 128 51 59; +#X obj -84 352 tcpserver; +#X text -118 351 and; +#X text 347 -55 tcpclient can connect to a server and send and receive +messages as lists of bytes. Any integer value between 0 and 255 can +be transmitted or received.; +#X msg -63 105 send ../doc/5.reference/test.txt; +#X obj -15 129 openpanel; +#X msg -15 153 send \$1; +#X obj -101 114 bng 15 250 50 0 empty empty empty 17 7 0 10 -24198 +-241291 -1; +#X text 172 105 send a file; +#X text 62 128 ...any file; +#X msg 99 251 71 69 84 32 104 116 116 112 58 47 47 47 105 110 100 101 +120 46 104 116 109 108 13 10; +#X text 529 257 'send' prefix is optional; +#X obj 199 514 spigot; +#X obj 238 491 tgl 15 0 empty empty enable_print 18 7 0 8 -24198 -241291 +-1 0 1; +#X obj 199 543 print >>>; +#X text 272 24 GET http:///index.phpCRLF; +#X floatatom 374 433 9 0 0 0 - - -; +#X text 173 291 set send-buffer size; +#X obj 374 407 route sent buf; +#X floatatom 421 457 9 0 0 0 - - -; +#X text 491 456 Size of the send buffer; +#X text 448 432 Number of bytes sent (may still be in buffer); +#X msg 147 315 buf; +#X text 177 314 get send-buffer size; +#X msg 123 291 buf 10; +#X msg 170 338 timeout 100; +#X text 258 337 set send timeout in microseconds (default is 1000) +; +#X msg 194 362 verbosity \$1; +#X obj 139 347 tgl 15 1 empty empty empty 0 -6 0 8 -4034 -257985 -1 +0 1; +#X text 289 362 print connection status messages to main window (default) +; +#X msg -136 16 send 71 69 84 32 104 116 116 112 58 47 47 47 105 110 +100 101 120 46 112 104 112 13 10 13 10; +#X text -208 540 2010/03/01 Martin Peach; +#X text 271 542 Attempting to print long messages can hang Pd!; +#X connect 0 0 8 0; +#X connect 1 0 2 0; +#X connect 1 1 3 0; +#X connect 1 2 4 0; +#X connect 1 3 5 0; +#X connect 7 0 8 0; +#X connect 8 0 39 0; +#X connect 8 1 1 0; +#X connect 8 2 9 0; +#X connect 8 3 45 0; +#X connect 10 0 8 0; +#X connect 11 0 10 0; +#X connect 12 0 8 0; +#X connect 13 0 8 0; +#X connect 15 0 8 0; +#X connect 20 0 8 0; +#X connect 27 0 8 0; +#X connect 31 0 8 0; +#X connect 32 0 33 0; +#X connect 33 0 8 0; +#X connect 34 0 32 0; +#X connect 37 0 8 0; +#X connect 39 0 41 0; +#X connect 40 0 39 1; +#X connect 45 0 43 0; +#X connect 45 1 46 0; +#X connect 49 0 8 0; +#X connect 51 0 8 0; +#X connect 52 0 8 0; +#X connect 54 0 8 0; +#X connect 55 0 54 0; +#X connect 57 0 8 0; diff --git a/tcpclient.c b/tcpclient.c new file mode 100644 index 0000000..93fbb91 --- /dev/null +++ b/tcpclient.c @@ -0,0 +1,552 @@ +/* tcpclient.c Martin Peach 20060508, working version 20060512 */ +/* linux version 20060515 */ +/* tcpclient.c is based on netclient: */ +/* -------------------------- netclient ------------------------------------- */ +/* */ +/* Extended 'netsend', connects to 'netserver'. */ +/* Uses child thread to connect to server. Thus needs pd0.35-test17 or later. */ +/* Written by Olaf Matthes (olaf.matthes@gmx.de) */ +/* Get source at http://www.akustische-kunst.org/puredata/maxlib/ */ +/* */ +/* 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, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* ---------------------------------------------------------------------------- */ +//define DEBUG + +#include "m_pd.h" +#include "s_stuff.h" + +#include +#include +#include +#include +#if defined(UNIX) || defined(unix) +#include +#include +#include +#include +#include +#include +#define SOCKET_ERROR -1 +#else +#include +#include /* for socklen_t */ +#endif + +#ifdef _MSC_VER +#define snprintf sprintf_s +#endif + +#define DEFPOLLTIME 20 /* check for input every 20 ms */ + +static t_class *tcpclient_class; +static char objName[] = "tcpclient"; +#define MAX_UDP_RECEIVE 65536L // longer than data in maximum UDP packet + +typedef struct _tcpclient +{ + t_object x_obj; + t_clock *x_clock; + t_clock *x_poll; + t_outlet *x_msgout; + t_outlet *x_addrout; + t_outlet *x_connectout; + t_outlet *x_statusout; + int x_dump; // 1 = hexdump received bytes + int x_verbosity; // 1 = post connection state changes to main window + int x_fd; // the socket + int x_fdbuf; // the socket's buffer size + t_int x_timeout_us; /* send timeout in microseconds */ + char *x_hostname; // address we want to connect to as text + int x_connectstate; // 0 = not connected, 1 = connected + int x_port; // port we're connected to + long x_addr; // address we're connected to as 32bit int + t_atom x_addrbytes[4]; // address we're connected to as 4 bytes + t_atom x_msgoutbuf[MAX_UDP_RECEIVE]; // received data as float atoms + unsigned char x_msginbuf[MAX_UDP_RECEIVE]; // received data as bytes + /* multithread stuff */ + pthread_t x_threadid; /* id of child thread */ + pthread_attr_t x_threadattr; /* attributes of child thread */ +} t_tcpclient; + +static void tcpclient_timeout(t_tcpclient *x, t_float timeout); +static void tcpclient_verbosity(t_tcpclient *x, t_float verbosity); +static void tcpclient_dump(t_tcpclient *x, t_float dump); +static void tcp_client_hexdump(t_tcpclient *x, long len); +static void tcpclient_tick(t_tcpclient *x); +static void *tcpclient_child_connect(void *w); +static void tcpclient_connect(t_tcpclient *x, t_symbol *hostname, t_floatarg fportno); +static void tcpclient_disconnect(t_tcpclient *x); +static void tcpclient_send(t_tcpclient *x, t_symbol *s, int argc, t_atom *argv); +int tcpclient_send_byte(t_tcpclient *x, char byte); +static int tcpclient_get_socket_send_buf_size(t_tcpclient *x); +static int tcpclient_set_socket_send_buf_size(t_tcpclient *x, int size); +static void tcpclient_buf_size(t_tcpclient *x, t_symbol *s, int argc, t_atom *argv); +static void tcpclient_rcv(t_tcpclient *x); +static void tcpclient_poll(t_tcpclient *x); +static void *tcpclient_new(t_floatarg udpflag); +static void tcpclient_free(t_tcpclient *x); +void tcpclient_setup(void); + +static void tcpclient_timeout(t_tcpclient *x, t_float timeout) +{ + /* set the timeout on the select call in tcpclient_send_byte */ + /* this is the maximum time in microseconds to wait */ + /* before abandoning attempt to send */ + + t_int timeout_us = 0; + if ((timeout >= 0)&&(timeout < 1000000)) + { + timeout_us = (t_int)timeout; + x->x_timeout_us = timeout_us; + } +} + +static void tcpclient_dump(t_tcpclient *x, t_float dump) +{ + x->x_dump = (dump == 0)?0:1; +} + +static void tcpclient_verbosity(t_tcpclient *x, t_float verbosity) +{ + x->x_verbosity = (verbosity == 0)?0:1; /* only two states so far */ +} + +static void tcp_client_hexdump(t_tcpclient *x, long len) +{ +#define BYTES_PER_LINE 16 + char hexStr[(3*BYTES_PER_LINE)+1]; + char ascStr[BYTES_PER_LINE+1]; + long i, j, k = 0L; + unsigned char *buf = x->x_msginbuf; + + if (x->x_verbosity) post("%s_hexdump %d:", objName, len); + while (k < len) + { + for (i = j = 0; i < BYTES_PER_LINE; ++i, ++k, j+=3) + { + if (k < len) + { + snprintf(&hexStr[j], 4, "%02X ", buf[k]); + snprintf(&ascStr[i], 2, "%c", ((buf[k] >= 32) && (buf[k] <= 126))? buf[k]: '.'); + } + else + { // the last line + snprintf(&hexStr[j], 4, " "); + snprintf(&ascStr[i], 2, " "); + } + } + post ("%s%s", hexStr, ascStr); + } +} + +static void tcpclient_tick(t_tcpclient *x) +{ + outlet_float(x->x_connectout, 1); +} + +static void *tcpclient_child_connect(void *w) +{ + t_tcpclient *x = (t_tcpclient*) w; + struct sockaddr_in server; + struct hostent *hp; + int sockfd; + + if (x->x_fd >= 0) + { + error("%s_connect: already connected", objName); + return (x); + } + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef DEBUG + post("%s: send socket %d\n", objName, sockfd); +#endif + if (sockfd < 0) + { + sys_sockerror("tcpclient: socket"); + return (x); + } + /* connect socket using hostname provided in command line */ + server.sin_family = AF_INET; + hp = gethostbyname(x->x_hostname); + if (hp == 0) + { + sys_sockerror("tcpclient: bad host?\n"); + return (x); + } + memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); + + /* assign client port number */ + server.sin_port = htons((u_short)x->x_port); + + if (x->x_verbosity) post("%s: connecting socket %d to port %d", objName, sockfd, x->x_port); + /* try to connect */ + if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) + { + sys_sockerror("tcpclient: connecting stream socket"); + sys_closesocket(sockfd); + return (x); + } + x->x_fd = sockfd; + x->x_addr = ntohl(*(long *)hp->h_addr); + /* outlet_float is not threadsafe ! */ + // outlet_float(x->x_obj.ob_outlet, 1); + x->x_connectstate = 1; + /* use callback instead to set outlet */ + clock_delay(x->x_clock, 0); + return (x); +} + +static void tcpclient_connect(t_tcpclient *x, t_symbol *hostname, t_floatarg fportno) +{ + /* we get hostname and port and pass them on + to the child thread that establishes the connection */ + x->x_hostname = hostname->s_name; + x->x_port = fportno; + x->x_connectstate = 0; + /* start child thread */ + if(pthread_create(&x->x_threadid, &x->x_threadattr, tcpclient_child_connect, x) < 0) + post("%s: could not create new thread", objName); +} + +static void tcpclient_disconnect(t_tcpclient *x) +{ + if (x->x_fd >= 0) + { + sys_closesocket(x->x_fd); + x->x_fd = -1; + x->x_connectstate = 0; + outlet_float(x->x_connectout, 0); + if (x->x_verbosity) post("%s: disconnected", objName); + } + else post("%s: not connected", objName); +} + +static void tcpclient_send(t_tcpclient *x, t_symbol *s, int argc, t_atom *argv) +{ +//#define BYTE_BUF_LEN 65536 // arbitrary maximum similar to max IP packet size +// static char byte_buf[BYTE_BUF_LEN]; + int i, j, d; + unsigned char c; + float f, e; + char *bp; + int length; + size_t sent; + int result; + char fpath[FILENAME_MAX]; + FILE *fptr; + t_atom output_atom; + +#ifdef DEBUG + post("s: %s", s->s_name); + post("argc: %d", argc); +#endif + + if (x->x_fd < 0) + { + error("%s: not connected", objName); + return; + } + for (i = j = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { + f = argv[i].a_w.w_float; + d = (int)f; + e = f - d; + if (e != 0) + { + error("%s_send: item %d (%f) is not an integer", objName, i, f); + return; + } + if ((d < 0) || (d > 255)) + { + error("%s: item %d (%f) is not between 0 and 255", objName, i, f); + return; + } + c = (unsigned char)d; + if (0 == tcpclient_send_byte(x, c)) break; + ++j; + } + else if (argv[i].a_type == A_SYMBOL) + { + + atom_string(&argv[i], fpath, FILENAME_MAX); + fptr = fopen(fpath, "rb"); + if (fptr == NULL) + { + post("%s_send: unable to open \"%s\"", objName, fpath); + return; + } + rewind(fptr); + while ((d = fgetc(fptr)) != EOF) + { + c = (char)(d & 0x0FF); + if (0 == tcpclient_send_byte(x, c)) break; + ++j; + } + fclose(fptr); + fptr = NULL; + if (x->x_verbosity) post("%s_send: read \"%s\" length %d byte%s", objName, fpath, j, ((d==1)?"":"s")); + } + else + { + error("%s_send: item %d is not a float or a file name", objName, i); + return; + } + } + sent = j; + SETFLOAT(&output_atom, sent); + outlet_anything( x->x_statusout, gensym("sent"), 1, &output_atom); +} + +int tcpclient_send_byte(t_tcpclient *x, char byte) +{ + int result = 0; + fd_set wfds; + struct timeval timeout; + + FD_ZERO(&wfds); + FD_SET(x->x_fd, &wfds); + timeout.tv_sec = 0; + timeout.tv_usec = x->x_timeout_us; /* give it about a millisecond to clear buffer */ + result = select(x->x_fd+1, NULL, &wfds, NULL, &timeout); + if (result == -1) + { + post("%s_send_byte: select returned error %d", objName, errno); + return 0; + } + if (FD_ISSET(x->x_fd, &wfds)) + { + result = send(x->x_fd, &byte, 1, 0); + if (result <= 0) + { + sys_sockerror("tcpclient: send"); + post("%s_send_byte: could not send data ", objName); + return 0; + } + } + return result; +} + +/* Return the send buffer size of socket, also output it on status outlet */ +static int tcpclient_get_socket_send_buf_size(t_tcpclient *x) +{ + int optVal = 0; + socklen_t optLen = sizeof(int); + t_atom output_atom; +#ifdef _WIN32 + if (getsockopt(x->x_fd, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen) == SOCKET_ERROR) + post("%_get_socket_send_buf_size: getsockopt returned %d\n", objName, WSAGetLastError()); +#else + if (getsockopt(x->x_fd, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen) == -1) + post("%_get_socket_send_buf_size: getsockopt returned %d\n", objName, errno); +#endif + SETFLOAT(&output_atom, optVal); + outlet_anything( x->x_statusout, gensym("buf"), 1, &output_atom); + return optVal; +} + +/* Set the send buffer size of socket, returns actual size */ +static int tcpclient_set_socket_send_buf_size(t_tcpclient *x, int size) +{ + int optVal = size; + int optLen = sizeof(int); +#ifdef _WIN32 + if (setsockopt(x->x_fd, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, optLen) == SOCKET_ERROR) + { + post("%s_set_socket_send_buf_size: setsockopt returned %d\n", objName, WSAGetLastError()); +#else + if (setsockopt(x->x_fd, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, optLen) == -1) + { + post("%s_set_socket_send_buf_size: setsockopt returned %d\n", objName, errno); +#endif + return 0; + } + else return (tcpclient_get_socket_send_buf_size(x)); +} + +/* Get/set the send buffer size of client socket */ +static void tcpclient_buf_size(t_tcpclient *x, t_symbol *s, int argc, t_atom *argv) +{ + int client = -1; + float buf_size = 0; + t_atom output_atom[3]; + + if(x->x_connectstate == 0) + { + post("%s_buf_size: no clients connected", objName); + return; + } + /* get size of buffer (first element in list) */ + if (argc > 0) + { + if (argv[0].a_type != A_FLOAT) + { + post("%s_buf_size: specify buffer size with a float", objName); + return; + } + buf_size = atom_getfloatarg(0, argc, argv); + x->x_fdbuf = tcpclient_set_socket_send_buf_size(x, (int)buf_size); + if (x->x_verbosity) post("%s_buf_size: set to %d", objName, x->x_fdbuf); + return; + } + x->x_fdbuf = tcpclient_get_socket_send_buf_size(x); + return; +} + +static void tcpclient_rcv(t_tcpclient *x) +{ + int sockfd = x->x_fd; + int ret; + int i; + fd_set readset; + fd_set exceptset; + struct timeval ztout; + + if(x->x_connectstate) + { + /* check if we can read/write from/to the socket */ + FD_ZERO(&readset); + FD_ZERO(&exceptset); + FD_SET(x->x_fd, &readset ); + FD_SET(x->x_fd, &exceptset ); + + ztout.tv_sec = 0; + ztout.tv_usec = 0; + + ret = select(sockfd+1, &readset, NULL, &exceptset, &ztout); + if(ret < 0) + { + error("%s: unable to read from socket", objName); + sys_closesocket(sockfd); + return; + } + if(FD_ISSET(sockfd, &readset) || FD_ISSET(sockfd, &exceptset)) + { + /* read from server */ + ret = recv(sockfd, x->x_msginbuf, MAX_UDP_RECEIVE, 0); + if(ret > 0) + { +#ifdef DEBUG + x->x_msginbuf[ret] = 0; + post("%s: received %d bytes ", objName, ret); +#endif + if (x->x_dump)tcp_client_hexdump(x, ret); + for (i = 0; i < ret; ++i) + { + /* convert the bytes in the buffer to floats in a list */ + x->x_msgoutbuf[i].a_w.w_float = (float)x->x_msginbuf[i]; + } + /* find sender's ip address and output it */ + x->x_addrbytes[0].a_w.w_float = (x->x_addr & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (x->x_addr & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (x->x_addr & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (x->x_addr & 0x0FF); + outlet_list(x->x_addrout, &s_list, 4L, x->x_addrbytes); + /* send the list out the outlet */ + if (ret > 1) outlet_list(x->x_msgout, &s_list, ret, x->x_msgoutbuf); + else outlet_float(x->x_msgout, x->x_msgoutbuf[0].a_w.w_float); + } + else + { + if (ret < 0) + { + sys_sockerror("tcpclient: recv"); + tcpclient_disconnect(x); + } + else + { + if (x->x_verbosity) post("%s: connection closed for socket %d\n", objName, sockfd); + tcpclient_disconnect(x); + } + } + } + } + else post("%s: not connected", objName); +} + +static void tcpclient_poll(t_tcpclient *x) +{ + if(x->x_connectstate) + tcpclient_rcv(x); /* try to read in case we're connected */ + clock_delay(x->x_poll, DEFPOLLTIME); /* see you later */ +} + +static void *tcpclient_new(t_floatarg udpflag) +{ + int i; + + t_tcpclient *x = (t_tcpclient *)pd_new(tcpclient_class); + x->x_msgout = outlet_new(&x->x_obj, &s_anything); /* received data */ + x->x_addrout = outlet_new(&x->x_obj, &s_list); + x->x_connectout = outlet_new(&x->x_obj, &s_float); /* connection state */ + x->x_statusout = outlet_new(&x->x_obj, &s_anything);/* last outlet for everything else */ + x->x_clock = clock_new(x, (t_method)tcpclient_tick); + x->x_poll = clock_new(x, (t_method)tcpclient_poll); + x->x_verbosity = 1; /* default post status changes to main window */ + x->x_fd = -1; + /* convert the bytes in the buffer to floats in a list */ + for (i = 0; i < MAX_UDP_RECEIVE; ++i) + { + x->x_msgoutbuf[i].a_type = A_FLOAT; + x->x_msgoutbuf[i].a_w.w_float = 0; + } + for (i = 0; i < 4; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + x->x_addr = 0L; + x->x_timeout_us = 1000; /* set a default 1ms send timeout */ + /* prepare child thread */ + if(pthread_attr_init(&x->x_threadattr) < 0) + post("%s: warning: could not prepare child thread", objName); + if(pthread_attr_setdetachstate(&x->x_threadattr, PTHREAD_CREATE_DETACHED) < 0) + post("%s: warning: could not prepare child thread", objName); + clock_delay(x->x_poll, 0); /* start polling the input */ + return (x); +} + +static void tcpclient_free(t_tcpclient *x) +{ + tcpclient_disconnect(x); + clock_free(x->x_poll); + clock_free(x->x_clock); +} + +void tcpclient_setup(void) +{ + tcpclient_class = class_new(gensym(objName), (t_newmethod)tcpclient_new, + (t_method)tcpclient_free, + sizeof(t_tcpclient), 0, A_DEFFLOAT, 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_connect, gensym("connect") + , A_SYMBOL, A_FLOAT, 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_disconnect, gensym("disconnect"), 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_send, gensym("send"), A_GIMME, 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_buf_size, gensym("buf"), A_GIMME, 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_rcv, gensym("receive"), 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_rcv, gensym("rcv"), 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_verbosity, gensym("verbosity"), A_FLOAT, 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_dump, gensym("dump"), A_FLOAT, 0); + class_addmethod(tcpclient_class, (t_method)tcpclient_timeout, gensym("timeout"), A_FLOAT, 0); + class_addlist(tcpclient_class, (t_method)tcpclient_send); +} + +/* end of tcpclient.c */ diff --git a/tcpreceive-help.pd b/tcpreceive-help.pd new file mode 100644 index 0000000..8cfbbe4 --- /dev/null +++ b/tcpreceive-help.pd @@ -0,0 +1,22 @@ +#N canvas 713 11 478 294 12; +#X floatatom 206 144 3 0 0 0 - - -; +#X floatatom 233 144 3 0 0 0 - - -; +#X floatatom 260 144 3 0 0 0 - - -; +#X floatatom 287 144 3 0 0 0 - - -; +#X text 163 143 from; +#X obj 155 185 print message; +#X obj 155 57 tcpreceive 9997; +#X floatatom 257 96 5 0 0 0 - - -; +#X text 303 94 connections; +#X text 32 16 tcpreceive receives bytes over a tcp connection.; +#X floatatom 315 144 5 0 0 0 - - -; +#X obj 206 117 unpack 0 0 0 0 0; +#X text 265 235 Martin Peach 2008/11/05; +#X connect 6 0 5 0; +#X connect 6 1 11 0; +#X connect 6 2 7 0; +#X connect 11 0 0 0; +#X connect 11 1 1 0; +#X connect 11 2 2 0; +#X connect 11 3 3 0; +#X connect 11 4 10 0; diff --git a/tcpreceive.c b/tcpreceive.c new file mode 100644 index 0000000..418e77b --- /dev/null +++ b/tcpreceive.c @@ -0,0 +1,333 @@ +/* x_net_tcpreceive.c 20060424. Martin Peach did it based on x_net.c. x_net.c header follows: */ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "m_pd.h" +#include "s_stuff.h" + +#ifdef _WIN32 +#include +#include /* for socklen_t */ +#else +#include +#include +#include +#include +#include +#include +#endif + + +/* ----------------------------- tcpreceive ------------------------- */ + +static t_class *tcpreceive_class; + +#define MAX_UDP_RECEIVE 65536L // longer than data in maximum UDP packet +#define MAX_CONNECTIONS 128 // this is going to cause trouble down the line...:( + +typedef struct _tcpconnection +{ + long addr; + unsigned short port; + int socket; +} t_tcpconnection; + +typedef struct _tcpreceive +{ + t_object x_obj; + t_outlet *x_msgout; + t_outlet *x_addrout; + t_outlet *x_connectout; + int x_connectsocket; + int x_nconnections; + t_tcpconnection x_connection[MAX_CONNECTIONS]; + t_atom x_addrbytes[5]; + t_atom x_msgoutbuf[MAX_UDP_RECEIVE]; + char x_msginbuf[MAX_UDP_RECEIVE]; +} t_tcpreceive; + +void tcpreceive_setup(void); +static void tcpreceive_free(t_tcpreceive *x); +static void *tcpreceive_new(t_floatarg fportno); +static void tcpreceive_read(t_tcpreceive *x, int sockfd); +static void tcpreceive_connectpoll(t_tcpreceive *x); +static int tcpreceive_addconnection(t_tcpreceive * x, int fd, long addr, unsigned short port); +static int tcpreceive_removeconnection(t_tcpreceive * x, int fd); +static void tcpreceive_closeall(t_tcpreceive *x); +static long tcpreceive_getconnection(t_tcpreceive * x, int fd); +static unsigned short tcpreceive_getconnectionport(t_tcpreceive * x, int fd); + +static void tcpreceive_read(t_tcpreceive *x, int sockfd) +{ + int i, read = 0; + long addr; + unsigned short port; + +// read = recvfrom(sockfd, x->x_msginbuf, MAX_UDP_RECEIVE, 0, (struct sockaddr *)&from, &fromlen); + read = recv(sockfd, x->x_msginbuf, MAX_UDP_RECEIVE, 0); +#ifdef DEBUG + post("tcpreceive_read: read %lu x->x_connectsocket = %d", + read, x->x_connectsocket); +#endif + if (read < 0) + { + sys_sockerror("tcpreceive_read: recv"); + sys_rmpollfn(sockfd); + sys_closesocket(sockfd); + tcpreceive_removeconnection(x, sockfd); + outlet_float(x->x_connectout, --x->x_nconnections); + } + else if (read == 0) + { + post("tcpreceive: EOF on socket %d\n", sockfd); + sys_rmpollfn(sockfd); + sys_closesocket(sockfd); + tcpreceive_removeconnection(x, sockfd); + outlet_float(x->x_connectout, --x->x_nconnections); + } + else if (read > 0) + { + for (i = 0; i < read; ++i) + { + /* convert the bytes in the buffer to floats in a list */ + x->x_msgoutbuf[i].a_w.w_float = (float)x->x_msginbuf[i]; + } + /* find sender's ip address and output it */ + addr = tcpreceive_getconnection(x, sockfd); + port = tcpreceive_getconnectionport(x, sockfd); + x->x_addrbytes[0].a_w.w_float = (addr & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (addr & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (addr & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (addr & 0x0FF); + x->x_addrbytes[4].a_w.w_float = port; + outlet_list(x->x_addrout, &s_list, 5L, x->x_addrbytes); + /* send the list out the outlet */ + if (read > 1) outlet_list(x->x_msgout, &s_list, read, x->x_msgoutbuf); + else outlet_float(x->x_msgout, x->x_msgoutbuf[0].a_w.w_float); + } +} + +static void *tcpreceive_new(t_floatarg fportno) +{ + t_tcpreceive *x; + struct sockaddr_in server; + int sockfd, portno = fportno; + int intarg, i; + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef DEBUG + post("tcpreceive_new: socket %d port %d", sockfd, portno); +#endif + if (sockfd < 0) + { + sys_sockerror("tcpreceive: socket"); + return (0); + } + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + + /* ask OS to allow another Pd to repoen this port after we close it. */ + intarg = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, + (char *)&intarg, sizeof(intarg)) < 0) + post("tcpreceive: setsockopt (SO_REUSEADDR) failed"); + /* Stream (TCP) sockets are set NODELAY */ + intarg = 1; + if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, + (char *)&intarg, sizeof(intarg)) < 0) + post("setsockopt (TCP_NODELAY) failed\n"); + + /* assign server port number */ + server.sin_port = htons((u_short)portno); + + /* name the socket */ + if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + { + sys_sockerror("tcpreceive: bind"); + sys_closesocket(sockfd); + return (0); + } + x = (t_tcpreceive *)pd_new(tcpreceive_class); + x->x_msgout = outlet_new(&x->x_obj, &s_anything); + x->x_addrout = outlet_new(&x->x_obj, &s_list); + x->x_connectout = outlet_new(&x->x_obj, &s_float); + /* clear the connection list */ + for (i = 0; i < MAX_CONNECTIONS; ++i) + { + x->x_connection[i].socket = -1; + x->x_connection[i].addr = 0L; + x->x_connection[i].port = 0; + } + /* convert the bytes in the buffer to floats in a list */ + for (i = 0; i < MAX_UDP_RECEIVE; ++i) + { + x->x_msgoutbuf[i].a_type = A_FLOAT; + x->x_msgoutbuf[i].a_w.w_float = 0; + } + for (i = 0; i < 5; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + + /* streaming protocol */ + if (listen(sockfd, 5) < 0) + { + sys_sockerror("tcpreceive: listen"); + sys_closesocket(sockfd); + sockfd = -1; + } + else + { + sys_addpollfn(sockfd, (t_fdpollfn)tcpreceive_connectpoll, x); + } + x->x_connectsocket = sockfd; + x->x_nconnections = 0; + +//udp version... sys_addpollfn(x->x_connectsocket, (t_fdpollfn)tcpreceive_read, x); + return (x); +} + +/* tcpreceive_connectpoll checks for incoming connection requests on the original socket */ +/* a new socket is assigned */ +static void tcpreceive_connectpoll(t_tcpreceive *x) +{ + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + long addr; + unsigned short port; + int fd; + + fd = accept(x->x_connectsocket, (struct sockaddr *)&from, &fromlen); + if (fd < 0) post("tcpreceive: accept failed"); + else + { + // t_socketreceiver *y = socketreceiver_new((void *)x, + // (t_socketnotifier)tcpreceive_notify, + // 0, 0); + + /* get the sender's ip */ + addr = ntohl(from.sin_addr.s_addr); + port = ntohs(from.sin_port); + if (tcpreceive_addconnection(x, fd, addr, port)) + { + sys_addpollfn(fd, (t_fdpollfn)tcpreceive_read, x); + outlet_float(x->x_connectout, ++x->x_nconnections); + x->x_addrbytes[0].a_w.w_float = (addr & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (addr & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (addr & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (addr & 0x0FF); + x->x_addrbytes[4].a_w.w_float = port; + outlet_list(x->x_addrout, &s_list, 5L, x->x_addrbytes); + } + else + { + error ("tcpreceive: Too many connections"); + sys_closesocket(fd); + } + } +} + +/* tcpreceive_addconnection tries to add the socket fd to the list */ +/* returns 1 on success, else 0 */ +static int tcpreceive_addconnection(t_tcpreceive *x, int fd, long addr, unsigned short port) +{ + int i; + for (i = 0; i < MAX_CONNECTIONS; ++i) + { + if (x->x_connection[i].socket == -1) + { + x->x_connection[i].socket = fd; + x->x_connection[i].addr = addr; + x->x_connection[i].port = port; + return 1; + } + } + return 0; +} + +/* tcpreceive_closeall closes all open sockets and deletes them from the list */ +static void tcpreceive_closeall(t_tcpreceive *x) +{ + int i; + + for (i = 0; ((i < MAX_CONNECTIONS) && (x->x_nconnections > 0)); ++i) + { + if (x->x_connection[i].socket != -1) + { + post ("tcpreceive: closing socket %d", x->x_connection[i].socket); + sys_rmpollfn(x->x_connection[i].socket); + sys_closesocket(x->x_connection[i].socket); + x->x_connection[i].socket = -1; + x->x_connection[i].addr = 0L; + x->x_connection[i].port = 0; + outlet_float(x->x_connectout, --x->x_nconnections); + } + } +} + +/* tcpreceive_removeconnection tries to delete the socket fd from the list */ +/* returns 1 on success, else 0 */ +static int tcpreceive_removeconnection(t_tcpreceive *x, int fd) +{ + int i; + for (i = 0; i < MAX_CONNECTIONS; ++i) + { + if (x->x_connection[i].socket == fd) + { + x->x_connection[i].socket = -1; + x->x_connection[i].addr = 0L; + x->x_connection[i].port = 0; + return 1; + } + } + return 0; +} + +/* tcpreceive_getconnectionport tries to find the socket fd in the list */ +/* returns port on success, else 0 */ +static u_short tcpreceive_getconnectionport(t_tcpreceive *x, int fd) +{ + int i; + for (i = 0; i < MAX_CONNECTIONS; ++i) + { + if (x->x_connection[i].socket == fd) + return x->x_connection[i].port; + } + return 0; +} + +/* tcpreceive_getconnection tries to find the socket fd in the list */ +/* returns addr on success, else 0 */ +static long tcpreceive_getconnection(t_tcpreceive *x, int fd) +{ + int i; + for (i = 0; i < MAX_CONNECTIONS; ++i) + { + if (x->x_connection[i].socket == fd) + return x->x_connection[i].addr; + } + return 0; +} + +static void tcpreceive_free(t_tcpreceive *x) +{ /* is this ever called? */ + if (x->x_connectsocket >= 0) + { + sys_rmpollfn(x->x_connectsocket); + sys_closesocket(x->x_connectsocket); + } + tcpreceive_closeall(x); +} + +void tcpreceive_setup(void) +{ + tcpreceive_class = class_new(gensym("tcpreceive"), + (t_newmethod)tcpreceive_new, (t_method)tcpreceive_free, + sizeof(t_tcpreceive), CLASS_NOINLET, A_DEFFLOAT, 0); +} + +/* end x_net_tcpreceive.c */ + diff --git a/tcpsend-help.pd b/tcpsend-help.pd new file mode 100644 index 0000000..8b3ee6d --- /dev/null +++ b/tcpsend-help.pd @@ -0,0 +1,31 @@ +#N canvas 0 94 559 320 12; +#X msg 85 175 disconnect; +#X msg 13 59 connect 127.0.0.1 9997; +#X obj 60 253 tgl 15 0 empty empty connected 20 7 0 8 -24198 -241291 +-1 0 1; +#X obj 60 230 tcpsend; +#X text 217 60 <--first; +#X msg 32 83 send 0 1 2 3; +#X text 8 7 tcpsend sends bytes over a tcp connection.; +#X text 8 30 Used in conjunction with packOSC will send OSC over tcp +; +#X msg 50 106 send ../doc/5.reference/test.txt; +#X obj 385 144 openpanel; +#X msg 385 168 send \$1; +#X obj 385 125 bng 15 250 50 0 empty empty empty 17 7 0 10 -24198 -241291 +-1; +#X text 344 105 send a file; +#X text 407 124 ...any file; +#X text 148 84 send raw data; +#X text 137 134 'send' prefix is optional; +#X msg 61 133 99 98 97; +#X text 291 260 Martin Peach 2007/06/20; +#X connect 0 0 3 0; +#X connect 1 0 3 0; +#X connect 3 0 2 0; +#X connect 5 0 3 0; +#X connect 8 0 3 0; +#X connect 9 0 10 0; +#X connect 10 0 3 0; +#X connect 11 0 9 0; +#X connect 16 0 3 0; diff --git a/tcpsend.c b/tcpsend.c new file mode 100644 index 0000000..b576eae --- /dev/null +++ b/tcpsend.c @@ -0,0 +1,254 @@ +/* tcpsend.c 20060424 Martin Peach did it based on x_net.c. x_net.c header follows: */ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* network */ + +#include "m_pd.h" +#include "s_stuff.h" +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#endif + +static t_class *tcpsend_class; + +typedef struct _tcpsend +{ + t_object x_obj; + int x_fd; +} t_tcpsend; + +void tcpsend_setup(void); +static void tcpsend_free(t_tcpsend *x); +static void tcpsend_send(t_tcpsend *x, t_symbol *s, int argc, t_atom *argv); +static void tcpsend_disconnect(t_tcpsend *x); +static void tcpsend_connect(t_tcpsend *x, t_symbol *hostname, t_floatarg fportno); +static void *tcpsend_new(void); + +static void *tcpsend_new(void) +{ + t_tcpsend *x = (t_tcpsend *)pd_new(tcpsend_class); + outlet_new(&x->x_obj, &s_float); + x->x_fd = -1; + return (x); +} + +static void tcpsend_connect(t_tcpsend *x, t_symbol *hostname, + t_floatarg fportno) +{ + struct sockaddr_in server; + struct hostent *hp; + int sockfd; + int portno = fportno; + int intarg; + + if (x->x_fd >= 0) + { + error("tcpsend: already connected"); + return; + } + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef DEBUG + fprintf(stderr, "tcpsend_connect: send socket %d\n", sockfd); +#endif + if (sockfd < 0) + { + sys_sockerror("tcpsend: socket"); + return; + } + /* connect socket using hostname provided in command line */ + server.sin_family = AF_INET; + hp = gethostbyname(hostname->s_name); + if (hp == 0) + { + post("tcpsend: bad host?\n"); + return; + } + /* for stream (TCP) sockets, specify "nodelay" */ + intarg = 1; + if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, + (char *)&intarg, sizeof(intarg)) < 0) + post("tcpsend: setsockopt (TCP_NODELAY) failed\n"); + + memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); + + /* assign client port number */ + server.sin_port = htons((u_short)portno); + + post("tcpsend: connecting to port %d", portno); + /* try to connect. */ + if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) + { + sys_sockerror("tcpsend: connecting stream socket"); + sys_closesocket(sockfd); + return; + } + x->x_fd = sockfd; + outlet_float(x->x_obj.ob_outlet, 1); +} + +static void tcpsend_disconnect(t_tcpsend *x) +{ + if (x->x_fd >= 0) + { + sys_closesocket(x->x_fd); + x->x_fd = -1; + outlet_float(x->x_obj.ob_outlet, 0); + post("tcpsend: disconnected"); + } +} + +static void tcpsend_send(t_tcpsend *x, t_symbol *s, int argc, t_atom *argv) +{ +#define BYTE_BUF_LEN 65536 // arbitrary maximum similar to max IP packet size + static char byte_buf[BYTE_BUF_LEN]; + int i, j; + unsigned int d; + unsigned char c; + float f, e; + char *bp; + int length, sent; + int result; + static double lastwarntime; + static double pleasewarn; + double timebefore; + double timeafter; + int late; + char fpath[FILENAME_MAX]; + FILE *fptr; + +#ifdef DEBUG + post("s: %s", s->s_name); + post("argc: %d", argc); +#endif + for (i = j = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { + f = argv[i].a_w.w_float; + d = (unsigned int)f; + e = f - d; + if (e != 0) + { + error("tcpsend_send: item %d (%f) is not an integer", i, f); + return; + } + c = (unsigned char)d; + if (c != d) + { + error("tcpsend_send: item %d (%f) is not between 0 and 255", i, f); + return; + } +#ifdef DEBUG + post("tcpsend_send: argv[%d]: %d", i, c); +#endif + byte_buf[j++] = c; + } + else if (argv[i].a_type == A_SYMBOL) + { + + atom_string(&argv[i], fpath, FILENAME_MAX); +#ifdef DEBUG + post ("tcpsend fname: %s", fpath); +#endif + fptr = fopen(fpath, "rb"); + if (fptr == NULL) + { + post("tcpsend: unable to open \"%s\"", fpath); + return; + } + rewind(fptr); +#ifdef DEBUG + post("tcpsend: d is %d", d); +#endif + while ((d = fgetc(fptr)) != EOF) + { + byte_buf[j++] = (char)(d & 0x0FF); +#ifdef DEBUG + post("tcpsend: byte_buf[%d] = %d", j-1, byte_buf[j-1]); +#endif + if (j >= BYTE_BUF_LEN) + { + post ("tcpsend: file too long, truncating at %lu", BYTE_BUF_LEN); + break; + } + } + fclose(fptr); + fptr = NULL; + post("tcpsend: read \"%s\" length %d byte%s", fpath, j, ((d==1)?"":"s")); + } + else + { + error("tcpsend_send: item %d is not a float or a file name", i); + return; + } + } + + length = j; + if ((x->x_fd >= 0) && (length > 0)) + { + for (bp = byte_buf, sent = 0; sent < length;) + { + timebefore = sys_getrealtime(); + result = send(x->x_fd, byte_buf, length-sent, 0); + timeafter = sys_getrealtime(); + late = (timeafter - timebefore > 0.005); + if (late || pleasewarn) + { + if (timeafter > lastwarntime + 2) + { + post("tcpsend blocked %d msec", + (int)(1000 * ((timeafter - timebefore) + pleasewarn))); + pleasewarn = 0; + lastwarntime = timeafter; + } + else if (late) pleasewarn += timeafter - timebefore; + } + if (result <= 0) + { + sys_sockerror("tcpsend"); + tcpsend_disconnect(x); + break; + } + else + { + sent += result; + bp += result; + } + } + } + else error("tcpsend: not connected"); +} + +static void tcpsend_free(t_tcpsend *x) +{ + tcpsend_disconnect(x); +} + +void tcpsend_setup(void) +{ + tcpsend_class = class_new(gensym("tcpsend"), (t_newmethod)tcpsend_new, + (t_method)tcpsend_free, + sizeof(t_tcpsend), 0, 0); + class_addmethod(tcpsend_class, (t_method)tcpsend_connect, + gensym("connect"), A_SYMBOL, A_FLOAT, 0); + class_addmethod(tcpsend_class, (t_method)tcpsend_disconnect, + gensym("disconnect"), 0); + class_addmethod(tcpsend_class, (t_method)tcpsend_send, gensym("send"), + A_GIMME, 0); + class_addlist(tcpsend_class, (t_method)tcpsend_send); +} + +/* end tcpsend.c */ diff --git a/tcpserver-help.pd b/tcpserver-help.pd new file mode 100644 index 0000000..5e7b041 --- /dev/null +++ b/tcpserver-help.pd @@ -0,0 +1,181 @@ +#N canvas 284 35 1256 779 12; +#X msg 143 -36 print; +#X floatatom 448 290 5 0 0 0 - - -; +#X floatatom 472 340 5 0 0 0 - - -; +#X obj 496 291 unpack 0 0 0 0; +#X floatatom 496 314 3 0 0 0 - - -; +#X floatatom 527 314 3 0 0 0 - - -; +#X floatatom 559 314 3 0 0 0 - - -; +#X floatatom 593 314 3 0 0 0 - - -; +#X text 453 313 from; +#X text 364 290 connections; +#X obj 425 415 print received; +#X msg 239 60 client 1 1 2 3; +#X msg 167 -12 broadcast 1 2 3; +#X text 195 -35 list of connections; +#X text 290 -12 send to all clients; +#X text 402 340 on socket; +#X msg 93 -86 send 504 1 2 3; +#X text 204 -91 send to client on socket 504; +#X msg 777 -209 client 1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 +18 19; +#X msg 777 -186 client 1 20 21 22 23 24 25 26 27 28 29 30 31 32 33 +34 35 36 37 38 39; +#X msg 777 -148 client 1 40 41 42 43 44 45 46 47 48 49 50 51 52 53 +54 55 56 57 58 59; +#X msg 777 -110 client 1 60 61 62 63 64 65 66 67 68 69 70 71 72 73 +74 75 76 77 78 79; +#X msg 777 -72 client 1 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 +95 96 97 98 99; +#X msg 777 -34 client 1 100 101 102 103 104 105 106 107 108 109 110 +111 112 113 114 115 116 117 118 119; +#X msg 777 4 client 1 120 121 122 123 124 125 126 127 138 139 140 141 +142 143 144 145 146 147 148 149; +#X msg 777 42 client 1 150 151 152 153 154 155 156 157 158 159 160 +161 162 163 164 165 166 167 168 169; +#X msg 777 80 client 1 170 171 172 173 174 175 176 177 178 179 180 +181 182 183 184 185 186 187 188 189; +#X msg 777 118 client 1 190 191 192 193 194 195 196 197 198 199 200 +201 202 203 204 205 206 207 208 209; +#X msg 777 156 client 1 210 211 212 213 214 215 216 217 218 219 220 +221 222 223 224 225 226 227 228 229; +#X msg 777 194 client 1 230 231 232 233 234 235 236 237 238 239 240 +241 242 243 244 245 246 247 248 249; +#X msg 777 232 client 1 250 251 252 253 254 255; +#X obj 144 -203 openpanel; +#X obj 144 -222 bng 15 250 50 0 empty empty choose_a_file 17 7 0 10 +-24198 -241291 -1; +#X text 128 -183 ...any file; +#X msg -4 -183 client 1 \$1; +#X msg 118 -61 10 1 2 43; +#X text 202 -74 'send' prefix is optional; +#X text 192 -57 (the first number is socket number); +#X msg 63 -116 disconnectsocket \$1; +#X msg 20 -159 disconnectclient \$1; +#X obj 777 257 s toserver; +#X obj 425 238 r toserver; +#X floatatom 247 -198 5 0 0 0 - - -; +#X obj 225 -179 f; +#X obj 225 -198 bng 15 250 50 0 empty empty empty 17 7 0 10 -258699 +-241291 -1; +#X floatatom 289 -155 5 0 0 0 - - -; +#X obj 267 -136 f; +#X obj 267 -155 bng 15 250 50 0 empty empty empty 17 7 0 10 -258699 +-241291 -1; +#X text -5 -136 disconnect by socket or client number; +#X text 532 265 argument is port number; +#X text 96 267 Received messages are output as lists of bytes; +#X text 35 371 ***WARNING*** Attempting to print long messages can +hang pd!; +#X obj 425 393 spigot; +#X obj 464 373 tgl 15 0 empty empty enable_print 17 7 0 10 -24198 -258699 +-45076 0 1; +#X msg 263 84 dump \$1; +#X obj 87 69 tgl 15 0 empty empty enable_dump 17 7 0 10 -4034 -257985 +-1 0 1; +#X text 19 19 dump received; +#X text 19 35 characters to main; +#X text 19 50 window in hexdump; +#X text 19 66 format:; +#X obj 425 266 tcpserver 9997; +#X msg -28 -207 client 1 test.txt; +#X text 777 -234 [tcpserver] sends and receives bytes on [0...255] +; +#X text 262 -243 [tcpserver] waits for clients to connect to its port. +; +#X text -33 -228 send a file to client 1; +#X msg 287 108 client 1 72 101 108 108 111 13 10; +#X floatatom 682 351 9 0 0 0 - - -; +#X obj 758 313 print status; +#X floatatom 642 374 5 0 0 0 - - -; +#X text 572 373 to_client; +#X msg 215 36 client; +#X msg 192 13 client 1; +#X text 260 12 get state of client 1; +#X text 353 60 send (binary) 1 2 3 to client 1; +#X obj 642 286 route sent client; +#X floatatom 817 443 5 0 0 0 - - -; +#X symbolatom 849 416 20 0 0 0 - - -; +#X floatatom 786 416 5 0 0 0 - - -; +#X text 749 350 bytes; +#X text 642 350 sent; +#X msg 313 134 clientbuf 1 65536; +#X obj 786 392 unpack 0 0 s 0; +#X floatatom 881 464 7 0 0 0 - - -; +#X text 726 464 length of send buffer:; +#X text 766 442 socket:; +#X text 735 415 client:; +#X text 826 415 ip:; +#X text 443 133 set send-buffer size for client 1; +#X text 267 36 get state of all clients (list on right outlet); +#X obj 642 325 unpack 0 0 0; +#X floatatom 723 392 5 0 0 0 - - -; +#X text 653 391 on_socket; +#X msg 363 184 send; +#X msg 388 209 1156; +#X text 406 183 output 'client' message for all sockets; +#X text 428 209 output 'client' message for socket 1156; +#X msg 338 159 timeout 100; +#X text 425 159 microsecond timeout for send (default is 1000); +#X text 35 450 2009/04/08 Martin Peach; +#X connect 0 0 60 0; +#X connect 3 0 4 0; +#X connect 3 1 5 0; +#X connect 3 2 6 0; +#X connect 3 3 7 0; +#X connect 11 0 60 0; +#X connect 12 0 60 0; +#X connect 16 0 60 0; +#X connect 18 0 40 0; +#X connect 19 0 40 0; +#X connect 20 0 40 0; +#X connect 21 0 40 0; +#X connect 22 0 40 0; +#X connect 23 0 40 0; +#X connect 24 0 40 0; +#X connect 25 0 40 0; +#X connect 26 0 40 0; +#X connect 27 0 40 0; +#X connect 28 0 40 0; +#X connect 29 0 40 0; +#X connect 30 0 40 0; +#X connect 31 0 34 0; +#X connect 32 0 31 0; +#X connect 34 0 60 0; +#X connect 35 0 60 0; +#X connect 38 0 60 0; +#X connect 39 0 60 0; +#X connect 41 0 60 0; +#X connect 42 0 43 1; +#X connect 43 0 39 0; +#X connect 44 0 43 0; +#X connect 45 0 46 1; +#X connect 46 0 38 0; +#X connect 47 0 46 0; +#X connect 52 0 10 0; +#X connect 53 0 52 1; +#X connect 54 0 60 0; +#X connect 55 0 54 0; +#X connect 60 0 52 0; +#X connect 60 1 1 0; +#X connect 60 2 2 0; +#X connect 60 3 3 0; +#X connect 60 4 74 0; +#X connect 61 0 60 0; +#X connect 65 0 60 0; +#X connect 70 0 60 0; +#X connect 71 0 60 0; +#X connect 74 0 89 0; +#X connect 74 1 81 0; +#X connect 74 2 67 0; +#X connect 80 0 60 0; +#X connect 81 0 77 0; +#X connect 81 1 75 0; +#X connect 81 2 76 0; +#X connect 81 3 82 0; +#X connect 89 0 68 0; +#X connect 89 1 66 0; +#X connect 89 2 90 0; +#X connect 92 0 60 0; +#X connect 93 0 60 0; +#X connect 96 0 60 0; diff --git a/tcpserver.c b/tcpserver.c new file mode 100644 index 0000000..5272029 --- /dev/null +++ b/tcpserver.c @@ -0,0 +1,1065 @@ +/* tcpserver.c Martin Peach 20060511 working version 20060512 */ +/* 20060515 works on linux too... */ +/* tcpserver.c is based on netserver: */ +/* -------------------------- netserver ------------------------------------- */ +/* */ +/* A server for bidirectional communication from within Pd. */ +/* Allows to send back data to specific clients connected to the server. */ +/* Written by Olaf Matthes */ +/* Get source at http://www.akustische-kunst.org/puredata/maxlib */ +/* */ +/* 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, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* ---------------------------------------------------------------------------- */ +//define DEBUG + +#include "m_pd.h" +#include "m_imp.h" +#include "s_stuff.h" + +#include +#include +#include +#if defined(UNIX) || defined(unix) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* linux has the SIOCOUTQ ioctl */ +#define SOCKET_ERROR -1 +#else +#include +#endif + + +#ifdef _MSC_VER +#define snprintf sprintf_s +#endif + +#define MAX_CONNECT 32 /* maximum number of connections */ +#define INBUFSIZE 65536L /* was 4096: size of receiving data buffer */ +#define MAX_UDP_RECEIVE 65536L /* longer than data in maximum UDP packet */ + +/* ----------------------------- tcpserver ------------------------- */ + +static t_class *tcpserver_class; +static char objName[] = "tcpserver"; + +typedef void (*t_tcpserver_socketnotifier)(void *x); +typedef void (*t_tcpserver_socketreceivefn)(void *x, t_binbuf *b); + +typedef struct _tcpserver_socketreceiver +{ + t_symbol *sr_host; + t_int sr_fd; + t_int sr_fdbuf; + u_long sr_addr; + unsigned char *sr_inbuf; + int sr_inhead; + int sr_intail; + void *sr_owner; + t_tcpserver_socketnotifier sr_notifier; + t_tcpserver_socketreceivefn sr_socketreceivefn; +} t_tcpserver_socketreceiver; + +typedef struct _tcpserver_send_params +{ + int client; + int sockfd; + char *byte_buf; + size_t length; + t_int timeout_us; +} t_tcpserver_send_params; + +typedef struct _tcpserver +{ + t_object x_obj; + t_outlet *x_msgout; + t_outlet *x_connectout; + t_outlet *x_sockout; + t_outlet *x_addrout; + t_outlet *x_status_outlet; + t_int x_dump; // 1 = hexdump received bytes + + t_tcpserver_socketreceiver *x_sr[MAX_CONNECT]; + + t_atom x_addrbytes[4]; + t_int x_sock_fd; + t_int x_connectsocket; + t_int x_nconnections; + t_int x_timeout_us; + t_atom x_msgoutbuf[MAX_UDP_RECEIVE]; + char x_msginbuf[MAX_UDP_RECEIVE]; +} t_tcpserver; + +static t_tcpserver_socketreceiver *tcpserver_socketreceiver_new(void *owner, t_tcpserver_socketnotifier notifier, + t_tcpserver_socketreceivefn socketreceivefn); +static int tcpserver_socketreceiver_doread(t_tcpserver_socketreceiver *x); +static void tcpserver_socketreceiver_read(t_tcpserver_socketreceiver *x, int fd); +static void tcpserver_socketreceiver_free(t_tcpserver_socketreceiver *x); +static void tcpserver_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcpserver_send_bytes(int sockfd, t_tcpserver *x, int argc, t_atom *argv); +#ifdef SIOCOUTQ +static int tcpserver_send_buffer_avaliable_for_client(t_tcpserver *x, int client); +#endif +static void *tcpserver_send_buf_thread(void *arg); +static size_t tcpserver_send_buf(int client, int sockfd, char *byte_buf, size_t length, t_int timeout_us); +static void tcpserver_client_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcpserver_output_client_state(t_tcpserver *x, int client); +static int tcpserver_get_socket_send_buf_size(int sockfd); +static int tcpserver_set_socket_send_buf_size(int sockfd, int size); +static void tcpserver_buf_size(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcpserver_disconnect(t_tcpserver *x); +static void tcpserver_client_disconnect(t_tcpserver *x, t_floatarg fclient); +static void tcpserver_socket_disconnect(t_tcpserver *x, t_floatarg fsocket); +static void tcpserver_broadcast(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcpserver_notify(t_tcpserver *x); +static void tcpserver_connectpoll(t_tcpserver *x); +static void tcpserver_print(t_tcpserver *x); +static void *tcpserver_new(t_floatarg fportno); +static void tcpserver_free(t_tcpserver *x); +void tcpserver_setup(void); +static void tcpserver_dump(t_tcpserver *x, t_float dump); +static void tcpserver_timeout(t_tcpserver *x, t_float timeout); +static void tcpserver_hexdump(unsigned char *buf, long len); + +static void tcpserver_timeout(t_tcpserver *x, t_float timeout) +{ + /* set the timeout on the select call in tcpserver_send_buf */ + /* this is the maximum time in microseconds to wait */ + /* before abandoning attempt to send */ + + t_int timeout_us = 0; + if ((timeout >= 0)&&(timeout < 1000000)) + { + timeout_us = (t_int)timeout; + x->x_timeout_us = timeout_us; + } +} + +static void tcpserver_dump(t_tcpserver *x, t_float dump) +{ + x->x_dump = (dump == 0)?0:1; +} + +static void tcpserver_hexdump(unsigned char *buf, long len) +{ +#define BYTES_PER_LINE 16 + char hexStr[(3*BYTES_PER_LINE)+1]; + char ascStr[BYTES_PER_LINE+1]; + long i, j, k = 0L; +#ifdef DEBUG + post("tcpserver_hexdump %d", len); +#endif + while (k < len) + { + for (i = j = 0; i < BYTES_PER_LINE; ++i, ++k, j+=3) + { + if (k < len) + { + snprintf(&hexStr[j], 4, "%02X ", buf[k]); + snprintf(&ascStr[i], 2, "%c", ((buf[k] >= 32) && (buf[k] <= 126))? buf[k]: '.'); + } + else + { // the last line + snprintf(&hexStr[j], 4, " "); + snprintf(&ascStr[i], 2, " "); + } + } + post ("%s%s", hexStr, ascStr); + } +} + +static t_tcpserver_socketreceiver *tcpserver_socketreceiver_new(void *owner, t_tcpserver_socketnotifier notifier, + t_tcpserver_socketreceivefn socketreceivefn) +{ + t_tcpserver_socketreceiver *x = (t_tcpserver_socketreceiver *)getbytes(sizeof(*x)); + if (!x) + { + error("%s_socketreceiver: unable to allocate %d bytes", objName, sizeof(*x)); + } + else + { + x->sr_inhead = x->sr_intail = 0; + x->sr_owner = owner; + x->sr_notifier = notifier; + x->sr_socketreceivefn = socketreceivefn; + if (!(x->sr_inbuf = malloc(INBUFSIZE))) + { + freebytes(x, sizeof(*x)); + x = NULL; + error("%s_socketreceiver: unable to allocate %ld bytes", objName, INBUFSIZE); + } + } + return (x); +} + +/* this is in a separately called subroutine so that the buffer isn't + sitting on the stack while the messages are getting passed. */ +static int tcpserver_socketreceiver_doread(t_tcpserver_socketreceiver *x) +{ + char messbuf[INBUFSIZE]; + char *bp = messbuf; + int indx, i; + int inhead = x->sr_inhead; + int intail = x->sr_intail; + unsigned char c; + t_tcpserver *y = x->sr_owner; + unsigned char *inbuf = x->sr_inbuf; + + if (intail == inhead) return (0); +#ifdef DEBUG + post ("%s_socketreceiver_doread: intail=%d inhead=%d", objName, intail, inhead); +#endif + + for (indx = intail, i = 0; indx != inhead; indx = (indx+1)&(INBUFSIZE-1), ++i) + { + c = *bp++ = inbuf[indx]; + y->x_msgoutbuf[i].a_w.w_float = (float)c; + } + + if (y->x_dump)tcpserver_hexdump(&inbuf[intail], i); + + if (i > 1) outlet_list(y->x_msgout, &s_list, i, y->x_msgoutbuf); + else outlet_float(y->x_msgout, y->x_msgoutbuf[0].a_w.w_float); + + // intail = (indx+1)&(INBUFSIZE-1); + x->sr_inhead = inhead; + x->sr_intail = indx;//intail; + return (1); +} + +static void tcpserver_socketreceiver_read(t_tcpserver_socketreceiver *x, int fd) +{ + int readto = (x->sr_inhead >= x->sr_intail ? INBUFSIZE : x->sr_intail-1); + int ret, i; + t_tcpserver *y = x->sr_owner; + + y->x_sock_fd = fd; + /* the input buffer might be full. If so, drop the whole thing */ + if (readto == x->sr_inhead) + { + post("%s: dropped message", objName); + x->sr_inhead = x->sr_intail = 0; + readto = INBUFSIZE; + } + else + { + ret = recv(fd, x->sr_inbuf + x->sr_inhead, + readto - x->sr_inhead, 0); + if (ret < 0) + { + sys_sockerror("tcpserver: recv"); + if (x->sr_notifier) (*x->sr_notifier)(x->sr_owner); + sys_rmpollfn(fd); + sys_closesocket(fd); + } + else if (ret == 0) + { + post("%s: connection closed on socket %d", objName, fd); + if (x->sr_notifier) (*x->sr_notifier)(x->sr_owner); + sys_rmpollfn(fd); + sys_closesocket(fd); + } + else + { +#ifdef DEBUG + post ("%s_socketreceiver_read: ret = %d", objName, ret); +#endif + x->sr_inhead += ret; + if (x->sr_inhead >= INBUFSIZE) x->sr_inhead = 0; + /* output client's IP and socket no. */ + for(i = 0; i < y->x_nconnections; i++) /* search for corresponding IP */ + { + if(y->x_sr[i]->sr_fd == y->x_sock_fd) + { +// outlet_symbol(x->x_connectionip, x->x_sr[i].sr_host); + /* find sender's ip address and output it */ + y->x_addrbytes[0].a_w.w_float = (y->x_sr[i]->sr_addr & 0xFF000000)>>24; + y->x_addrbytes[1].a_w.w_float = (y->x_sr[i]->sr_addr & 0x0FF0000)>>16; + y->x_addrbytes[2].a_w.w_float = (y->x_sr[i]->sr_addr & 0x0FF00)>>8; + y->x_addrbytes[3].a_w.w_float = (y->x_sr[i]->sr_addr & 0x0FF); + outlet_list(y->x_addrout, &s_list, 4L, y->x_addrbytes); + break; + } + } + outlet_float(y->x_sockout, y->x_sock_fd); /* the socket number */ + tcpserver_socketreceiver_doread(x); + } + } +} + +static void tcpserver_socketreceiver_free(t_tcpserver_socketreceiver *x) +{ + if (x != NULL) + { + free(x->sr_inbuf); + freebytes(x, sizeof(*x)); + } +} + +/* ---------------- main tcpserver (send) stuff --------------------- */ + +static void tcpserver_send_bytes(int client, t_tcpserver *x, int argc, t_atom *argv) +{ + static char byte_buf[MAX_UDP_RECEIVE];// arbitrary maximum similar to max IP packet size + int i, j, d; + unsigned char c; + float f, e; + int length; + size_t flen = 0; + int sockfd = x->x_sr[client]->sr_fd; + char fpath[FILENAME_MAX]; + FILE *fptr; + t_atom output_atom[3]; + t_tcpserver_send_params *ttsp; + pthread_t sender_thread; + pthread_attr_t sender_attr; + int sender_thread_result; + + /* process & send data */ + if(sockfd >= 0) + { + /* sender thread should start out detached so its resouces will be freed when it is done */ + if (0!= (sender_thread_result = pthread_attr_init(&sender_attr))) + { + error("%s: pthread_attr_init failed: %d", objName, sender_thread_result); + goto failed; + } + if(0!= (sender_thread_result = pthread_attr_setdetachstate(&sender_attr, PTHREAD_CREATE_DETACHED))) + { + error("%s: pthread_attr_setdetachstate failed: %d", objName, sender_thread_result); + goto failed; + } + for (i = j = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { /* load floats into buffer as long as they are integers on [0..255]*/ + f = argv[i].a_w.w_float; + d = (int)f; + e = f - d; +#ifdef DEBUG + post("%s: argv[%d]: float:%f int:%d delta:%f", objName, i, f, d, e); +#endif + if (e != 0) + { + error("%s: item %d (%f) is not an integer", objName, i, f); + goto failed; + } + if ((d < 0) || (d > 255)) + { + error("%s: item %d (%f) is not between 0 and 255", objName, i, f); + goto failed; + } + c = (unsigned char)d; /* make sure it doesn't become negative; this only matters for post() */ +#ifdef DEBUG + post("%s: argv[%d]: %d", objName, i, c); +#endif + byte_buf[j++] = c; + if (j >= MAX_UDP_RECEIVE) + { /* if the argument list is longer than our buffer, send the buffer whenever it's full */ +#ifdef SIOCOUTQ + if (tcpserver_send_buffer_avaliable_for_client(x, client) < j) + { + error("%s: buffer too small for client(%d)", objName, client); + goto failed; + } +#endif // SIOCOUTQ + ttsp = (t_tcpserver_send_params *)getbytes(sizeof(t_tcpserver_send_params)); + if (ttsp == NULL) + { + error("%s: unable to allocate %d bytes for t_tcpserver_send_params", objName, sizeof(t_tcpserver_send_params)); + goto failed; + } + ttsp->client = client; + ttsp->sockfd = sockfd; + ttsp->byte_buf = byte_buf; + ttsp->length = j; + ttsp->timeout_us = x->x_timeout_us; + if (0 != (sender_thread_result = pthread_create(&sender_thread, &sender_attr, tcpserver_send_buf_thread, (void *)ttsp))) + { + error("%s: couldn't create sender thread (%d)", objName, sender_thread_result); + goto failed; + } + flen += j; + j = 0; + } + } + else if (argv[i].a_type == A_SYMBOL) + { /* symbols are interpreted to be file names; attempt to load the file and send it */ + + atom_string(&argv[i], fpath, FILENAME_MAX); +#ifdef DEBUG + post ("%s: fname: %s", objName, fpath); +#endif + fptr = fopen(fpath, "rb"); + if (fptr == NULL) + { + error("%s: unable to open \"%s\"", objName, fpath); + goto failed; + } + rewind(fptr); +#ifdef DEBUG + post("%s: d is %d", objName, d); +#endif + while ((d = fgetc(fptr)) != EOF) + { + byte_buf[j++] = (char)(d & 0x0FF); +#ifdef DEBUG + post("%s: byte_buf[%d] = %d", objName, j-1, byte_buf[j-1]); +#endif + if (j >= MAX_UDP_RECEIVE) + { /* if the file is longer than our buffer, send the buffer whenever it's full */ + /* this might be better than allocating huge amounts of memory */ +#ifdef SIOCOUTQ + if (tcpserver_send_buffer_avaliable_for_client(x, client) < j) + { + error("%s: buffer too small for client(%d)", objName, client); + goto failed; + } +#endif // SIOCOUTQ + ttsp = (t_tcpserver_send_params *)getbytes(sizeof(t_tcpserver_send_params)); + if (ttsp == NULL) + { + error("%s: unable to allocate %d bytes for t_tcpserver_send_params", objName, sizeof(t_tcpserver_send_params)); + goto failed; + } + ttsp->client = client; + ttsp->sockfd = sockfd; + ttsp->byte_buf = byte_buf; + ttsp->length = j; + ttsp->timeout_us = x->x_timeout_us; + if ( 0!= (sender_thread_result = pthread_create(&sender_thread, &sender_attr, tcpserver_send_buf_thread, (void *)ttsp))) + { + error("%s: couldn't create sender thread (%d)", objName, sender_thread_result); + goto failed; + } + flen += j; + j = 0; + } + } + flen += j; + fclose(fptr); + fptr = NULL; + post("%s: read \"%s\" length %d byte%s", objName, fpath, flen, ((d==1)?"":"s")); + } + else + { /* arg was neither a float nor a valid file name */ + error("%s: item %d is not a float or a file name", objName, i); + goto failed; + } + } + length = j; + if (length > 0) + { /* send whatever remains in our buffer */ +#ifdef SIOCOUTQ + if (tcpserver_send_buffer_avaliable_for_client(x, client) < length) + { + error("%s: buffer too small for client(%d)", objName, client); + goto failed; + } +#endif // SIOCOUTQ + ttsp = (t_tcpserver_send_params *)getbytes(sizeof(t_tcpserver_send_params)); + if (ttsp == NULL) + { + error("%s: unable to allocate %d bytes for t_tcpserver_send_params", objName, sizeof(t_tcpserver_send_params)); + goto failed; + } + ttsp->client = client; + ttsp->sockfd = sockfd; + ttsp->byte_buf = byte_buf; + ttsp->length = length; + ttsp->timeout_us = x->x_timeout_us; + if ( 0!= (sender_thread_result = pthread_create(&sender_thread, &sender_attr, tcpserver_send_buf_thread, (void *)ttsp))) + { + error("%s: couldn't create sender thread (%d)", objName, sender_thread_result); + goto failed; + } + flen += length; + } + } + else post("%s: not a valid socket number (%d)", objName, sockfd); +failed: + SETFLOAT(&output_atom[0], client+1); + SETFLOAT(&output_atom[1], flen); + SETFLOAT(&output_atom[2], sockfd); + outlet_anything( x->x_status_outlet, gensym("sent"), 3, output_atom); +} + +#ifdef SIOCOUTQ +/* SIOCOUTQ exists only(?) on linux, returns remaining space in the socket's output buffer */ +static int tcpserver_send_buffer_avaliable_for_client(t_tcpserver *x, int client) +{ + int sockfd = x->x_sr[client].sr_fd; + int result = 0L; + + ioctl(sockfd, SIOCOUTQ, &result); + return result; +} +#endif // SIOCOUTQ + +// send a buffer in its own thread +static void *tcpserver_send_buf_thread(void *arg) +{ + t_tcpserver_send_params *ttsp = (t_tcpserver_send_params *)arg; + int result; + + result = send(ttsp->sockfd, ttsp->byte_buf, ttsp->length, 0); + if (result <= 0) + { + sys_sockerror("tcpserver: send"); + post("%s_send_buf: could not send data to client %d", objName, ttsp->client+1); + } + freebytes (arg, sizeof (t_tcpserver_send_params)); + return NULL; +} + +// send a buffer one byte at a time, no thread +static size_t tcpserver_send_buf(int client, int sockfd, char *byte_buf, size_t length, t_int timeout_us) +{ + char *bp; + size_t sent = 0; + int result; + fd_set wfds; + struct timeval timeout; + + for (bp = byte_buf, sent = 0; sent < length;) + { + FD_ZERO(&wfds); + FD_SET(sockfd, &wfds); + timeout.tv_sec = 0; + timeout.tv_usec = timeout_us; /* give it a short time to clear buffer */ + result = select(sockfd+1, NULL, &wfds, NULL, &timeout); + if (result == -1) + { + post("%s_send_buf: select returned error %d", objName, errno); + break; + } + if (FD_ISSET(sockfd, &wfds)) + { + result = send(sockfd, bp, 1, 0);/*(sockfd, bp, (int)(length-sent), 0);*/ + if (result <= 0) + { + sys_sockerror("tcpserver: send"); + post("%s_send_buf: could not send data to client %d", objName, client+1); + break; + } + else + { + sent += result; + bp += result; + } + } + else + { + post ("%s_send_buf: can't send right now, sent %lu of %lu", objName, sent, length); + return sent;/* abandon any further attempts to send so we don't block */ + } + } + return sent; +} + +/* send message to client using socket number */ +static void tcpserver_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int i, sockfd; + int client = -1; + + if(x->x_nconnections <= 0) + { + post("%s_send: no clients connected", objName); + return; + } + if(argc == 0) /* no socket specified: output state of all sockets */ + { + tcpserver_output_client_state(x, client); + return; + } + /* get socket number of connection (first element in list) */ + if(argv[0].a_type == A_FLOAT) + { + sockfd = atom_getfloatarg(0, argc, argv); + for(i = 0; i < x->x_nconnections; i++) /* check if connection exists */ + { + if(x->x_sr[i]->sr_fd == sockfd) + { + client = i; /* the client we're sending to */ + break; + } + } + if(client == -1) + { + post("%s_send: no connection on socket %d", objName, sockfd); + return; + } + } + else + { + post("%s_send: no socket specified", objName); + return; + } + if (argc < 2) /* nothing to send: output state of this socket */ + { + tcpserver_output_client_state(x, client+1); + return; + } + tcpserver_send_bytes(client, x, argc-1, &argv[1]); +} + +/* disconnect the client at x_sock_fd */ +static void tcpserver_disconnect(t_tcpserver *x) +{ + int i, fd; + t_tcpserver_socketreceiver *y; + + if (x->x_sock_fd >= 0) + { + /* find the socketreceiver for this socket */ + for(i = 0; i < x->x_nconnections; i++) + { + if(x->x_sr[i]->sr_fd == x->x_sock_fd) + { + y = x->x_sr[i]; + fd = y->sr_fd; + if (y->sr_notifier) (*y->sr_notifier)(x); + sys_rmpollfn(fd); + sys_closesocket(fd); + x->x_sock_fd = -1; + return; + } + } + } + post("%s__disconnect: no connection on socket %d", objName, x->x_sock_fd); +} + +/* disconnect a client by socket */ +static void tcpserver_socket_disconnect(t_tcpserver *x, t_floatarg fsocket) +{ + int sock = (int)fsocket; + + if(x->x_nconnections <= 0) + { + post("%s_socket_disconnect: no clients connected", objName); + return; + } + x->x_sock_fd = sock; + tcpserver_disconnect(x); +} + +/* disconnect a client by number */ +static void tcpserver_client_disconnect(t_tcpserver *x, t_floatarg fclient) +{ + int client = (int)fclient; + + if(x->x_nconnections <= 0) + { + post("%s_client_disconnect: no clients connected", objName); + return; + } + if (!((client > 0) && (client < MAX_CONNECT))) + { + post("%s: client %d out of range [1..%d]", objName, client, MAX_CONNECT); + return; + } + --client;/* zero based index*/ + x->x_sock_fd = x->x_sr[client]->sr_fd; + tcpserver_disconnect(x); +} + + +/* send message to client using client number + note that the client numbers might change in case a client disconnects! */ +/* clients start at 1 but our index starts at 0 */ +static void tcpserver_client_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int client = -1; + + if(x->x_nconnections <= 0) + { + post("%s_client_send: no clients connected", objName); + return; + } + if(argc > 0) + { + /* get number of client (first element in list) */ + if(argv[0].a_type == A_FLOAT) + client = atom_getfloatarg(0, argc, argv); + else + { + post("%s_client_send: specify client by number", objName); + return; + } + if (!((client > 0) && (client < MAX_CONNECT))) + { + post("%s_client_send: client %d out of range [1..%d]", objName, client, MAX_CONNECT); + return; + } + } + if (argc > 1) + { + --client;/* zero based index*/ + tcpserver_send_bytes(client, x, argc-1, &argv[1]); + return; + } + tcpserver_output_client_state(x, client); +} + +static void tcpserver_output_client_state(t_tcpserver *x, int client) +{ + t_atom output_atom[4]; + + if (client == -1) + { + /* output parameters of all connections via status outlet */ + for(client = 0; client < x->x_nconnections; client++) + { + x->x_sr[client]->sr_fdbuf = tcpserver_get_socket_send_buf_size(x->x_sr[client]->sr_fd); + SETFLOAT(&output_atom[0], client+1); + SETFLOAT(&output_atom[1], x->x_sr[client]->sr_fd); + output_atom[2].a_type = A_SYMBOL; + output_atom[2].a_w.w_symbol = x->x_sr[client]->sr_host; + SETFLOAT(&output_atom[3], x->x_sr[client]->sr_fdbuf); + outlet_anything( x->x_status_outlet, gensym("client"), 4, output_atom); + } + } + else + { + client -= 1;/* zero-based client index conflicts with 1-based user index !!! */ + /* output client parameters via status outlet */ + x->x_sr[client]->sr_fdbuf = tcpserver_get_socket_send_buf_size(x->x_sr[client]->sr_fd); + SETFLOAT(&output_atom[0], client+1);/* user sees client 0 as 1 */ + SETFLOAT(&output_atom[1], x->x_sr[client]->sr_fd); + output_atom[2].a_type = A_SYMBOL; + output_atom[2].a_w.w_symbol = x->x_sr[client]->sr_host; + SETFLOAT(&output_atom[3], x->x_sr[client]->sr_fdbuf); + outlet_anything( x->x_status_outlet, gensym("client"), 4, output_atom); + } +} + +/* Return the send buffer size of socket */ +static int tcpserver_get_socket_send_buf_size(int sockfd) +{ + int optVal = 0; + unsigned int optLen = sizeof(int); +#ifdef _WIN32 + if (getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen) == SOCKET_ERROR) + post("%_get_socket_send_buf_size: getsockopt returned %d\n", objName, WSAGetLastError()); +#else + if (getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen) == -1) + post("%_get_socket_send_buf_size: getsockopt returned %d\n", objName, errno); +#endif + return optVal; +} + +/* Set the send buffer size of socket, returns actual size */ +static int tcpserver_set_socket_send_buf_size(int sockfd, int size) +{ + int optVal = size; + int optLen = sizeof(int); +#ifdef _WIN32 + if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, optLen) == SOCKET_ERROR) + { + post("%s_set_socket_send_buf_size: setsockopt returned %d\n", objName, WSAGetLastError()); +#else + if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, optLen) == -1) + { + post("%s_set_socket_send_buf_size: setsockopt returned %d\n", objName, errno); +#endif + return 0; + } + else return (tcpserver_get_socket_send_buf_size(sockfd)); +} + +/* Get/set the send buffer of client socket */ +static void tcpserver_buf_size(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int client = -1; + float buf_size = 0; + t_atom output_atom[3]; + + if(x->x_nconnections <= 0) + { + post("%s_buf_size: no clients connected", objName); + return; + } + /* get number of client (first element in list) */ + if (argc > 0) + { + if (argv[0].a_type == A_FLOAT) + client = atom_getfloatarg(0, argc, argv); + else + { + post("%s_buf_size: specify client by number", objName); + return; + } + if (!((client > 0) && (client < MAX_CONNECT))) + { + post("%s__buf_size: client %d out of range [1..%d]", objName, client, MAX_CONNECT); + return; + } + } + if (argc > 1) + { + if (argv[1].a_type != A_FLOAT) + { + post("%s_buf_size: specify buffer size with a float", objName); + return; + } + buf_size = atom_getfloatarg(1, argc, argv); + --client;/* zero based index*/ + x->x_sr[client]->sr_fdbuf = tcpserver_set_socket_send_buf_size(x->x_sr[client]->sr_fd, (int)buf_size); + post("%s_buf_size: client %d set to %d", objName, client+1, x->x_sr[client]->sr_fdbuf); + return; + } + post("%s_buf_size: specify client and buffer size", objName); + return; +} + +/* broadcasts a message to all connected clients */ +static void tcpserver_broadcast(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int client; + /* enumerate through the clients and send each the message */ + for(client = 0; client < x->x_nconnections; client++) /* check if connection exists */ + { + if(x->x_sr[client]->sr_fd >= 0) + { /* socket exists for this client */ + tcpserver_send_bytes(client, x, argc, argv); + } + } +} + +/* ---------------- main tcpserver (receive) stuff --------------------- */ + +static void tcpserver_notify(t_tcpserver *x) +{ + int i, k; + + /* remove connection from list */ + for(i = 0; i < x->x_nconnections; i++) + { + if(x->x_sr[i]->sr_fd == x->x_sock_fd) + { + x->x_nconnections--; + post("%s: \"%s\" removed from list of clients", objName, x->x_sr[i]->sr_host->s_name); + tcpserver_socketreceiver_free(x->x_sr[i]); + x->x_sr[i] = NULL; + + /* rearrange list now: move entries to close the gap */ + for(k = i; k < x->x_nconnections; k++) + { + x->x_sr[k] = x->x_sr[k + 1]; + } + } + } + outlet_float(x->x_connectout, x->x_nconnections); +} + +static void tcpserver_connectpoll(t_tcpserver *x) +{ + struct sockaddr_in incomer_address; + unsigned int sockaddrl = sizeof( struct sockaddr ); + int fd = accept(x->x_connectsocket, (struct sockaddr*)&incomer_address, &sockaddrl); + int i; + int optVal; + unsigned int optLen = sizeof(int); + + if (fd < 0) post("%s: accept failed", objName); + else + { + t_tcpserver_socketreceiver *y = tcpserver_socketreceiver_new((void *)x, + (t_tcpserver_socketnotifier)tcpserver_notify, NULL);/* MP tcpserver_doit isn't used I think...*/ + if (!y) + { +#ifdef _WIN32 + closesocket(fd); +#else + close(fd); +#endif + return; + } + sys_addpollfn(fd, (t_fdpollfn)tcpserver_socketreceiver_read, y); + x->x_nconnections++; + i = x->x_nconnections - 1; + x->x_sr[i] = y; + x->x_sr[i]->sr_host = gensym(inet_ntoa(incomer_address.sin_addr)); + x->x_sr[i]->sr_fd = fd; + post("%s: accepted connection from %s on socket %d", + objName, x->x_sr[i]->sr_host->s_name, x->x_sr[i]->sr_fd); +/* see how big the send buffer is on this socket */ + x->x_sr[i]->sr_fdbuf = 0; +#ifdef _WIN32 + if (getsockopt(x->x_sr[i]->sr_fd, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen) != SOCKET_ERROR) + { + /* post("%s_connectpoll: send buffer is %ld\n", objName, optVal); */ + x->x_sr[i]->sr_fdbuf = optVal; + } + else post("%s_connectpoll: getsockopt returned %d\n", objName, WSAGetLastError()); +#else + if (getsockopt(x->x_sr[i]->sr_fd, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen) == 0) + { + /* post("%s_connectpoll: send buffer is %ld\n", objName, optVal); */ + x->x_sr[i]->sr_fdbuf = optVal; + } + else post("%s_connectpoll: getsockopt returned %d\n", objName, errno); +#endif + outlet_float(x->x_connectout, x->x_nconnections); + outlet_float(x->x_sockout, x->x_sr[i]->sr_fd); /* the socket number */ + x->x_sr[i]->sr_addr = ntohl(incomer_address.sin_addr.s_addr); + x->x_addrbytes[0].a_w.w_float = (x->x_sr[i]->sr_addr & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (x->x_sr[i]->sr_addr & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (x->x_sr[i]->sr_addr & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (x->x_sr[i]->sr_addr & 0x0FF); + outlet_list(x->x_addrout, &s_list, 4L, x->x_addrbytes); + } +} + +static void tcpserver_print(t_tcpserver *x) +{ + int i; + + if(x->x_nconnections > 0) + { + post("%s: %d open connections:", objName, x->x_nconnections); + for(i = 0; i < x->x_nconnections; i++) + { + post(" \"%s\" on socket %d", + x->x_sr[i]->sr_host->s_name, x->x_sr[i]->sr_fd); + } + } + else post("%s: no open connections", objName); +} + +static void *tcpserver_new(t_floatarg fportno) +{ + t_tcpserver *x; + int i; + struct sockaddr_in server; + int sockfd, portno = fportno; + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef DEBUG + post("%s: receive socket %d", objName, sockfd); +#endif + if (sockfd < 0) + { + sys_sockerror("tcpserver: socket"); + return (0); + } + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; +#ifdef IRIX + /* this seems to work only in IRIX but is unnecessary in + Linux. Not sure what NT needs in place of this. */ + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0) + post("setsockopt failed\n"); +#endif + /* assign server port number */ + server.sin_port = htons((u_short)portno); + /* name the socket */ + if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + { + sys_sockerror("tcpserver: bind"); + sys_closesocket(sockfd); + return (0); + } + x = (t_tcpserver *)pd_new(tcpserver_class); + x->x_msgout = outlet_new(&x->x_obj, &s_anything); /* 1st outlet for received data */ + /* streaming protocol */ + if (listen(sockfd, 5) < 0) + { + sys_sockerror("tcpserver: listen"); + sys_closesocket(sockfd); + sockfd = -1; + } + else + { + sys_addpollfn(sockfd, (t_fdpollfn)tcpserver_connectpoll, x); + x->x_connectout = outlet_new(&x->x_obj, &s_float); /* 2nd outlet for number of connected clients */ + x->x_sockout = outlet_new(&x->x_obj, &s_float); /* 3rd outlet for socket number of current client */ + x->x_addrout = outlet_new(&x->x_obj, &s_list); /* 4th outlet for ip address of current client */ + x->x_status_outlet = outlet_new(&x->x_obj, &s_anything);/* 5th outlet for everything else */ + } + x->x_connectsocket = sockfd; + x->x_nconnections = 0; + for(i = 0; i < MAX_CONNECT; i++) + { + x->x_sr[i] = NULL; + } + /* prepare to convert the bytes in the buffer to floats in a list */ + for (i = 0; i < MAX_UDP_RECEIVE; ++i) + { + x->x_msgoutbuf[i].a_type = A_FLOAT; + x->x_msgoutbuf[i].a_w.w_float = 0; + } + for (i = 0; i < 4; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + x->x_timeout_us = 1000;/* default 1 ms for select call timeout when sending */ + return (x); +} + +static void tcpserver_free(t_tcpserver *x) +{ + int i; + + for(i = 0; i < MAX_CONNECT; i++) + { + + if (NULL!=x->x_sr[i]) { + tcpserver_socketreceiver_free(x->x_sr[i]); + if (x->x_sr[i]->sr_fd >= 0) + { + sys_rmpollfn(x->x_sr[i]->sr_fd); + sys_closesocket(x->x_sr[i]->sr_fd); + } + } + } + if (x->x_connectsocket >= 0) + { + sys_rmpollfn(x->x_connectsocket); + sys_closesocket(x->x_connectsocket); + } + +} + +void tcpserver_setup(void) +{ + tcpserver_class = class_new(gensym(objName),(t_newmethod)tcpserver_new, (t_method)tcpserver_free, + sizeof(t_tcpserver), 0, A_DEFFLOAT, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_print, gensym("print"), 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_send, gensym("send"), A_GIMME, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_client_send, gensym("client"), A_GIMME, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_buf_size, gensym("clientbuf"), A_GIMME, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_client_disconnect, gensym("disconnectclient"), A_DEFFLOAT, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_socket_disconnect, gensym("disconnectsocket"), A_DEFFLOAT, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_dump, gensym("dump"), A_FLOAT, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_broadcast, gensym("broadcast"), A_GIMME, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_timeout, gensym("timeout"), A_FLOAT, 0); + class_addlist(tcpserver_class, (t_method)tcpserver_send); +} + +/* end of tcpserver.c */ diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..e36d4ff --- /dev/null +++ b/test.txt @@ -0,0 +1 @@ +testing one two three diff --git a/udpreceive-help.pd b/udpreceive-help.pd new file mode 100644 index 0000000..a906e52 --- /dev/null +++ b/udpreceive-help.pd @@ -0,0 +1,19 @@ +#N canvas 187 585 478 280 12; +#X floatatom 209 142 3 0 0 0 - - -; +#X floatatom 236 142 3 0 0 0 - - -; +#X floatatom 263 142 3 0 0 0 - - -; +#X floatatom 290 142 3 0 0 0 - - -; +#X text 166 141 from; +#X obj 107 186 print message; +#X obj 107 91 udpreceive 9997; +#X text 32 16 udpreceive receives bytes over a udp connection.; +#X floatatom 318 142 5 0 0 0 - - -; +#X obj 209 116 unpack 0 0 0 0 0; +#X text 265 235 Martin Peach 2008/11/05; +#X connect 6 0 5 0; +#X connect 6 1 9 0; +#X connect 9 0 0 0; +#X connect 9 1 1 0; +#X connect 9 2 2 0; +#X connect 9 3 3 0; +#X connect 9 4 8 0; diff --git a/udpreceive.c b/udpreceive.c new file mode 100644 index 0000000..a9ac791 --- /dev/null +++ b/udpreceive.c @@ -0,0 +1,160 @@ +/* x_net_udpreceive.c 20060424. Martin Peach did it based on x_net.c. x_net.c header follows: */ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "m_pd.h" +#include "s_stuff.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif + + +/* ----------------------------- udpreceive ------------------------- */ + +static t_class *udpreceive_class; + +#define MAX_UDP_RECEIVE 65536L // longer than data in maximum UDP packet + +typedef struct _udpreceive +{ + t_object x_obj; + t_outlet *x_msgout; + t_outlet *x_addrout; + int x_connectsocket; + t_atom x_addrbytes[5]; + t_atom x_msgoutbuf[MAX_UDP_RECEIVE]; + char x_msginbuf[MAX_UDP_RECEIVE]; +} t_udpreceive; + +void udpreceive_setup(void); +static void udpreceive_free(t_udpreceive *x); +static void *udpreceive_new(t_floatarg fportno); +static void udpreceive_read(t_udpreceive *x, int sockfd); + +static void udpreceive_read(t_udpreceive *x, int sockfd) +{ + int i, read = 0; + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + long addr; + unsigned short port; + + read = recvfrom(sockfd, x->x_msginbuf, MAX_UDP_RECEIVE, 0, (struct sockaddr *)&from, &fromlen); +#ifdef DEBUG + post("udpreceive_read: read %lu x->x_connectsocket = %d", + read, x->x_connectsocket); +#endif + /* get the sender's ip */ + addr = ntohl(from.sin_addr.s_addr); + port = ntohs(from.sin_port); + + x->x_addrbytes[0].a_w.w_float = (addr & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (addr & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (addr & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (addr & 0x0FF); + x->x_addrbytes[4].a_w.w_float = port; + outlet_list(x->x_addrout, &s_list, 5L, x->x_addrbytes); + + if (read < 0) + { + sys_sockerror("udpreceive_read"); + sys_closesocket(x->x_connectsocket); + return; + } + if (read > 0) + { + for (i = 0; i < read; ++i) + { + /* convert the bytes in the buffer to floats in a list */ + x->x_msgoutbuf[i].a_w.w_float = (float)(unsigned char)x->x_msginbuf[i]; + } + /* send the list out the outlet */ + if (read > 1) outlet_list(x->x_msgout, &s_list, read, x->x_msgoutbuf); + else outlet_float(x->x_msgout, x->x_msgoutbuf[0].a_w.w_float); + } +} + +static void *udpreceive_new(t_floatarg fportno) +{ + t_udpreceive *x; + struct sockaddr_in server; + int sockfd, portno = fportno; + int intarg, i; + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_DGRAM, 0); +#ifdef DEBUG + post("udpreceive_new: socket %d port %d", sockfd, portno); +#endif + if (sockfd < 0) + { + sys_sockerror("udpreceive: socket"); + return (0); + } + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + + /* enable delivery of all multicast or broadcast (but not unicast) + * UDP datagrams to all sockets bound to the same port */ + intarg = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, + (char *)&intarg, sizeof(intarg)) < 0) + post("udpreceive: setsockopt (SO_REUSEADDR) failed"); + + /* assign server port number */ + server.sin_port = htons((u_short)portno); + + /* name the socket */ + if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + { + sys_sockerror("udpreceive: bind"); + sys_closesocket(sockfd); + return (0); + } + x = (t_udpreceive *)pd_new(udpreceive_class); + x->x_msgout = outlet_new(&x->x_obj, &s_anything); + x->x_addrout = outlet_new(&x->x_obj, &s_list); + x->x_connectsocket = sockfd; + + /* convert the bytes in the buffer to floats in a list */ + for (i = 0; i < MAX_UDP_RECEIVE; ++i) + { + x->x_msgoutbuf[i].a_type = A_FLOAT; + x->x_msgoutbuf[i].a_w.w_float = 0; + } + for (i = 0; i < 5; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + sys_addpollfn(x->x_connectsocket, (t_fdpollfn)udpreceive_read, x); + return (x); +} + +static void udpreceive_free(t_udpreceive *x) +{ + if (x->x_connectsocket >= 0) + { + sys_rmpollfn(x->x_connectsocket); + sys_closesocket(x->x_connectsocket); + } +} + +void udpreceive_setup(void) +{ + udpreceive_class = class_new(gensym("udpreceive"), + (t_newmethod)udpreceive_new, (t_method)udpreceive_free, + sizeof(t_udpreceive), CLASS_NOINLET, A_DEFFLOAT, 0); +} + +/* end udpreceive.c */ diff --git a/udpreceive~.c b/udpreceive~.c new file mode 100644 index 0000000..42bf7c7 --- /dev/null +++ b/udpreceive~.c @@ -0,0 +1,805 @@ +/* udpreceive~ started 20100110 by Martin Peach based on netreceive~: */ +/* ------------------------ netreceive~ --------------------------------------- */ +/* */ +/* Tilde object to receive uncompressed audio data from netsend~. */ +/* Written by Olaf Matthes . */ +/* Based on streamin~ by Guenter Geiger. */ +/* Get source at http://www.akustische-kunst.org/ */ +/* */ +/* 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. */ +/* */ +/* See file LICENSE for further informations on licensing terms. */ +/* */ +/* 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, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* This project was commissioned by the Society for Arts and Technology [SAT], */ +/* Montreal, Quebec, Canada, http://www.sat.qc.ca/. */ +/* */ +/* ---------------------------------------------------------------------------- */ + + +#include "m_pd.h" + +#include "udpsend~.h" + +#include +#include +#if defined(UNIX) || defined(unix) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SOCKET_ERROR -1 +#endif +#ifdef _WIN32 +#include +#include /* for socklen_t */ +#endif + +#ifndef SOL_IP +#define SOL_IP IPPROTO_IP +#endif + +#define DEFAULT_AUDIO_BUFFER_FRAMES 16 /* a small circ. buffer for 16 frames */ +#define DEFAULT_AVERAGE_NUMBER 10 /* number of values we store for average history */ +#define DEFAULT_NETWORK_POLLTIME 1 /* interval in ms for polling for input data (Max/MSP only) */ +#define DEFAULT_QUEUE_LENGTH 3 /* min. number of buffers that can be used reliably on your hardware */ + +#if defined(UNIX) || defined(unix) +#define CLOSESOCKET(fd) close(fd) +#endif +#ifdef _WIN32 +#define CLOSESOCKET(fd) closesocket(fd) +#endif + +/* ------------------------ udpreceive~ ----------------------------- */ + +typedef struct _udpreceive_tilde +{ + t_object x_obj; + t_outlet *x_outlet1; + t_outlet *x_outlet2; + t_outlet *x_addrout; + t_clock *x_clock; + t_atom x_addrbytes[5]; + int x_socket; + int x_connectsocket; + int x_nconnections; + long x_addr; + unsigned short x_port; + t_symbol *x_hostname; + int x_error; + int x_buffering; + char x_msg[256]; + + /* buffering */ + int x_framein;// index of next empty frame in x_frames[] + int x_frameout;// index of next frame to play back from x_frames[] + t_frame x_frames[DEFAULT_AUDIO_BUFFER_FRAMES]; + int x_maxframes; + long x_tag_errors; + int x_sync;// zero if we didn't receive a tag when we expected one + int x_blocksize; + int x_blocksperrecv; + int x_blockssincerecv; + + int x_nbytes; + int x_counter;// count of received frames + int x_average[DEFAULT_AVERAGE_NUMBER]; + int x_averagecur; + int x_underflow; + int x_overflow; + int x_valid; + long x_samplerate; + int x_noutlets; + int x_vecsize; + t_int **x_myvec; /* vector we pass on to the DSP routine */ +} t_udpreceive_tilde; + +/* function prototypes */ +static void udpreceive_tilde_closesocket(t_udpreceive_tilde* x); +static void udpreceive_tilde_reset(t_udpreceive_tilde* x, t_floatarg buffer); +static void udpreceive_tilde_datapoll(t_udpreceive_tilde *x); +static void udpreceive_tilde_connectpoll(t_udpreceive_tilde *x); +static int udpreceive_tilde_createsocket(t_udpreceive_tilde* x, int portno); +static t_int *udpreceive_tilde_perform(t_int *w); +static void udpreceive_tilde_dsp(t_udpreceive_tilde *x, t_signal **sp); +static void udpreceive_tilde_info(t_udpreceive_tilde *x); +static void udpreceive_tilde_tick(t_udpreceive_tilde *x); +static void *udpreceive_tilde_new(t_floatarg fportno, t_floatarg outlets, t_floatarg blocksize); +static void udpreceive_tilde_free(t_udpreceive_tilde *x); +void udpreceive_tilde_setup(void); +static int udpreceive_tilde_sockerror(char *s); +static int udpreceive_tilde_setsocketoptions(int sockfd); +/* these would require to include some headers that are different + between pd 0.36 and later, so it's easier to do it like this! */ +EXTERN void sys_rmpollfn(int fd); +EXTERN void sys_addpollfn(int fd, void* fn, void *ptr); + +static t_class *udpreceive_tilde_class; +static t_symbol *ps_format, *ps_channels, *ps_framesize, *ps_overflow, *ps_underflow, *ps_packets, + *ps_queuesize, *ps_average, *ps_sf_float, *ps_sf_16bit, *ps_sf_8bit, + *ps_sf_mp3, *ps_sf_aac, *ps_sf_unknown, *ps_bitrate, *ps_hostname, *ps_nothing, + *ps_tag_errors; + +/* remove all pollfunctions and close socket */ +static void udpreceive_tilde_closesocket(t_udpreceive_tilde* x) +{ + sys_rmpollfn(x->x_socket); + outlet_float(x->x_outlet1, 0); + CLOSESOCKET(x->x_socket); + x->x_socket = -1; +} + +static void udpreceive_tilde_reset(t_udpreceive_tilde* x, t_floatarg buffer) +{ + int i; + + x->x_counter = 0; + x->x_nbytes = 0; + x->x_framein = 0; + x->x_frameout = 0; + x->x_blockssincerecv = 0; + x->x_blocksperrecv = x->x_blocksize / x->x_vecsize; + x->x_tag_errors = 0; + + for (i = 0; i < DEFAULT_AVERAGE_NUMBER; i++) + x->x_average[i] = x->x_maxframes; + x->x_averagecur = 0; + + i = (int)buffer; + if ((i > 0)&&(i < DEFAULT_AUDIO_BUFFER_FRAMES)) + { + x->x_maxframes = i; + post("udpreceive~: set buffer to %d frames)", x->x_maxframes); + } + else if (i != 0) /* special case of 0 leaves buffer size unchanged */ + { + post("udpreceive~: buffer must be between 1 and %d frames)", DEFAULT_AUDIO_BUFFER_FRAMES-1); + } + x->x_underflow = 0; + x->x_overflow = 0; + x->x_buffering = 1; +} + +static void udpreceive_tilde_datapoll(t_udpreceive_tilde *x) +{ + int ret; + int n; + long addr; + unsigned short port; + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + t_tag *tag_ptr; + + n = x->x_nbytes; + + if (x->x_nbytes == 0) /* we ate all the samples and need a new header tag */ + { + tag_ptr = &x->x_frames[x->x_framein].tag; + /* receive header tag */ + ret = recvfrom(x->x_socket, (char*)tag_ptr, sizeof(t_tag), 0, + (struct sockaddr *)&from, &fromlen); + /* get the sender's ip */ + addr = ntohl(from.sin_addr.s_addr); + port = ntohs(from.sin_port); + /* output addr/port only if changed */ + if (!((addr == x->x_addr)&&(port == x->x_port))) + { + x->x_addr = addr; + x->x_port = port; + x->x_addrbytes[0].a_w.w_float = (x->x_addr & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (x->x_addr & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (x->x_addr & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (x->x_addr & 0x0FF); + x->x_addrbytes[4].a_w.w_float = x->x_port; + outlet_list(x->x_addrout, &s_list, 5L, x->x_addrbytes); + } + if (ret <= 0) /* error */ + { + if (0 == udpreceive_tilde_sockerror("recv tag")) return; + udpreceive_tilde_reset(x, 0); + return; + } + else if (ret != sizeof(t_tag)) + { + /* incomplete header tag: return and try again later */ + /* in the hope that more data will be available */ + error("udpreceive~: got incomplete header tag"); + return; + } + /* make sure this is really a tag */ + if (!((tag_ptr->tag[0] == 'T')&&(tag_ptr->tag[1] == 'A')&&(tag_ptr->tag[2] == 'G')&&(tag_ptr->tag[3] == '!'))) + { + ++x->x_tag_errors; + if (x->x_sync) error("udpreceive~: bad header tag (%d)", x->x_tag_errors); + x->x_sync = 0; + /* tag length is 16 bytes, a multiple of the data frame size, so eventually we should resync on a tag */ + return; + } + /* adjust byte order if necessary */ + tag_ptr->count = ntohl(tag_ptr->count); + tag_ptr->framesize = ntohl(tag_ptr->framesize); + + /* get info from header tag */ + if (tag_ptr->channels > (x->x_noutlets-1)) + { + error("udpreceive~: incoming stream has too many channels (%d)", tag_ptr->channels); + x->x_counter = 0; + return; + } + x->x_nbytes = n = tag_ptr->framesize; + x->x_sync = 1; + } + else /* we already have header tag or some data and need more */ + { + ret = recvfrom(x->x_socket, (char*)x->x_frames[x->x_framein].data + x->x_frames[x->x_framein].tag.framesize - n, + n, 0, (struct sockaddr *)&from, &fromlen); + if (ret > 0) + { + n -= ret; + } + else if (ret < 0) /* error */ + { + if ( 0 == (ret = udpreceive_tilde_sockerror("recv data"))) return; +#ifdef _WIN32 + if ( ret == WSAEFAULT) +#else + if ( ret == EFAULT) +#endif + { + post ("udpreceive~: EFAULT: %p %lu %d", x->x_frames[x->x_framein].data, x->x_frames[x->x_framein].tag.framesize, n); + return; + } + udpreceive_tilde_reset(x, 0); + return; + } + + x->x_nbytes = n; + if (n == 0) /* a complete packet is received */ + { + if (x->x_frames[x->x_framein].tag.format == SF_AAC) + { + error("udpreceive~: don't know how to decode AAC format"); + return; + } + x->x_counter++; + x->x_framein++; + x->x_framein %= DEFAULT_AUDIO_BUFFER_FRAMES; + + /* check for buffer overflow */ + if (x->x_framein == x->x_frameout) + { + x->x_overflow++; + } + } + } +} + +static void udpreceive_tilde_connectpoll(t_udpreceive_tilde *x) +{ + socklen_t sockaddrlen = sizeof(struct sockaddr); + struct sockaddr_in incomer_address; + int fd = accept(x->x_connectsocket, (struct sockaddr*)&incomer_address, &sockaddrlen); + + if (fd < 0) + { + post("udpreceive~: accept failed"); + return; + } + if (x->x_socket != -1) + { + post("udpreceive~: new connection"); + udpreceive_tilde_closesocket(x); + } + + udpreceive_tilde_reset(x, 0); + x->x_socket = fd; + x->x_nbytes = 0; + x->x_hostname = gensym(inet_ntoa(incomer_address.sin_addr)); + sys_addpollfn(fd, udpreceive_tilde_datapoll, x); + outlet_float(x->x_outlet1, 1); +} + +static int udpreceive_tilde_createsocket(t_udpreceive_tilde* x, int portno) +{ + struct sockaddr_in server; + int sockfd; + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + + if (sockfd < 0) + { + udpreceive_tilde_sockerror("socket"); + return 0; + } + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + + /* assign server port number */ + + server.sin_port = htons((u_short)portno); + post("udpreceive~: listening to port number %d", portno); + + udpreceive_tilde_setsocketoptions(sockfd); + + /* name the socket */ + if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + { + udpreceive_tilde_sockerror("bind"); + CLOSESOCKET(sockfd); + return 0; + } + + x->x_socket = sockfd; + x->x_nbytes = 0; + sys_addpollfn(sockfd, udpreceive_tilde_datapoll, x); + return 1; +} + +/* Queue is 1 to 16 frames long */ +#define QUEUESIZE (int)((x->x_framein + DEFAULT_AUDIO_BUFFER_FRAMES - x->x_frameout) % DEFAULT_AUDIO_BUFFER_FRAMES) +/* Block is a set of sample vectors inside a frame, one vector per channel */ +#define BLOCKOFFSET (x->x_blockssincerecv * x->x_vecsize * x->x_frames[x->x_frameout].tag.channels) + +static t_int *udpreceive_tilde_perform(t_int *w) +{ + t_udpreceive_tilde *x = (t_udpreceive_tilde*) (w[1]); + int n = (int)(w[2]); + t_float *out[DEFAULT_AUDIO_CHANNELS]; + const int offset = 3; + const int channels = x->x_frames[x->x_frameout].tag.channels; + int i = 0; + + x->x_valid = 0; + for (i = 0; i < x->x_noutlets; i++) + { + out[i] = (t_float *)(w[offset + i]); + } + + /* set our vector size to the local vector size */ + if (n != x->x_vecsize) + { + x->x_vecsize = n; + x->x_blocksperrecv = x->x_blocksize / x->x_vecsize; + x->x_blockssincerecv = 0; + } + + /* check whether there is enough data in buffer */ + if (x->x_buffering && (x->x_counter < x->x_maxframes)) + { + goto bail; + } + x->x_buffering = 0; + + /* check for buffer underflow */ + if (x->x_framein == x->x_frameout) + { + x->x_underflow++; + goto bail; + } + + x->x_valid = 1; + /* queue balancing */ + x->x_average[x->x_averagecur] = QUEUESIZE; + if (++x->x_averagecur >= DEFAULT_AVERAGE_NUMBER) + x->x_averagecur = 0; + + switch (x->x_frames[x->x_frameout].tag.format) + { + case SF_FLOAT: + { + int32_t* buf = (int32_t *)x->x_frames[x->x_frameout].data + BLOCKOFFSET; + flint fl; + + /* swap bytes if necessary */ + while (n--) + { + for (i = 0; i < channels; i++) + { + fl.i32 = ntohl(*buf++); + *out[i]++ = fl.f32; + } + for (i = channels; i < (x->x_noutlets-1); i++) + { + *out[i]++ = 0.; + } + *out[i]++ = x->x_valid; + } + x->x_error = 0; + break; + } + case SF_16BIT: + { + short* buf = (short *)x->x_frames[x->x_frameout].data + BLOCKOFFSET; + /* swap bytes if necessary */ + while (n--) + { + for (i = 0; i < channels; i++) + { + *out[i]++ = (t_float)((short)(ntohs(*buf++)) * 3.051850e-05); + } + for (i = channels; i < (x->x_noutlets-1); i++) + { + *out[i]++ = 0.; + } + *out[i]++ = x->x_valid; + } + x->x_error = 0; + break; + } + case SF_8BIT: + { + unsigned char* buf = (unsigned char *)x->x_frames[x->x_frameout].data + BLOCKOFFSET; + + while (n--) + { + for (i = 0; i < channels; i++) + { + *out[i]++ = (t_float)((0.0078125 * (*buf++)) - 1.0); + } + for (i = channels; i < (x->x_noutlets-1); i++) + { + *out[i]++ = 0.; + } + *out[i]++ = x->x_valid; + } + x->x_error = 0; + break; + } + case SF_MP3: + { + if (x->x_error != 1) + { + x->x_error = 1; + sprintf(x->x_msg, "udpreceive~: mp3 format not supported"); + clock_delay(x->x_clock, 0); + } + break; + } + case SF_AAC: + { + if (x->x_error != 2) + { + x->x_error = 2; + sprintf(x->x_msg, "udpreceive~: aac format not supported"); + clock_delay(x->x_clock, 0); + } + break; + } + default: + if (x->x_error != 3) + { + x->x_error = 3; + sprintf(x->x_msg, "udpreceive~: unknown format (%d)",x->x_frames[x->x_frameout].tag.format); + clock_delay(x->x_clock, 0); + } + break; + } + + if (!(x->x_blockssincerecv < x->x_blocksperrecv - 1)) + { + x->x_blockssincerecv = 0; + x->x_frameout++; + x->x_frameout %= DEFAULT_AUDIO_BUFFER_FRAMES; + } + else x->x_blockssincerecv++; + + return (w + offset + x->x_noutlets); + +bail: + /* set output to zero */ + while (n--) for (i = 0; i < x->x_noutlets; i++) *(out[i]++) = 0.; + return (w + offset + x->x_noutlets); +} + +static void udpreceive_tilde_dsp(t_udpreceive_tilde *x, t_signal **sp) +{ + int i; + + x->x_myvec[0] = (t_int*)x; + x->x_myvec[1] = (t_int*)sp[0]->s_n; + + x->x_samplerate = (long)sp[0]->s_sr; + + if (x->x_blocksize % sp[0]->s_n) + { + error("udpreceive~: signal vector size too large (needs to be even divisor of %d)", x->x_blocksize); + } + else + { + for (i = 0; i < x->x_noutlets; i++) + { + x->x_myvec[2 + i] = (t_int*)sp[i + 1]->s_vec; + } + dsp_addv(udpreceive_tilde_perform, x->x_noutlets + 2, (t_int*)x->x_myvec); + } +} + +/* send stream info */ +static void udpreceive_tilde_info(t_udpreceive_tilde *x) +{ + t_atom list[2]; + t_symbol *sf_format; + t_float bitrate; + int i, avg = 0; + + for (i = 0; i < DEFAULT_AVERAGE_NUMBER; i++) + avg += x->x_average[i]; + + bitrate = (t_float)((SF_SIZEOF(x->x_frames[x->x_frameout].tag.format) * x->x_samplerate * 8 * x->x_frames[x->x_frameout].tag.channels) / 1000.); + + switch (x->x_frames[x->x_frameout].tag.format) + { + case SF_FLOAT: + { + sf_format = ps_sf_float; + break; + } + case SF_16BIT: + { + sf_format = ps_sf_16bit; + break; + } + case SF_8BIT: + { + sf_format = ps_sf_8bit; + break; + } + case SF_MP3: + { + sf_format = ps_sf_mp3; + break; + } + case SF_AAC: + { + sf_format = ps_sf_aac; + break; + } + default: + { + sf_format = ps_sf_unknown; + break; + } + } + + /* --- stream information (t_tag) --- */ + /* audio format */ + SETSYMBOL(list, (t_symbol *)sf_format); + outlet_anything(x->x_outlet2, ps_format, 1, list); + + /* channels */ + SETFLOAT(list, (t_float)x->x_frames[x->x_frameout].tag.channels); + outlet_anything(x->x_outlet2, ps_channels, 1, list); + + /* framesize */ + SETFLOAT(list, (t_float)x->x_frames[x->x_frameout].tag.framesize); + outlet_anything(x->x_outlet2, ps_framesize, 1, list); + + /* bitrate */ + SETFLOAT(list, (t_float)bitrate); + outlet_anything(x->x_outlet2, ps_bitrate, 1, list); + + /* --- internal info (buffer and network) --- */ + /* overflow */ + SETFLOAT(list, (t_float)x->x_overflow); + outlet_anything(x->x_outlet2, ps_overflow, 1, list); + + /* underflow */ + SETFLOAT(list, (t_float)x->x_underflow); + outlet_anything(x->x_outlet2, ps_underflow, 1, list); + + /* queuesize */ + SETFLOAT(list, (t_float)QUEUESIZE); + outlet_anything(x->x_outlet2, ps_queuesize, 1, list); + + /* average queuesize */ + SETFLOAT(list, (t_float)((t_float)avg / (t_float)DEFAULT_AVERAGE_NUMBER)); + outlet_anything(x->x_outlet2, ps_average, 1, list); + + /* total packets */ + SETFLOAT(list, (t_float)x->x_counter); + outlet_anything(x->x_outlet2, ps_packets, 1, list); + + /* total tag errors */ + SETFLOAT(list, (t_float)x->x_tag_errors); + outlet_anything(x->x_outlet2, ps_tag_errors, 1, list); + + outlet_list(x->x_addrout, &s_list, 5L, x->x_addrbytes); +} + +static void udpreceive_tilde_tick(t_udpreceive_tilde *x) +{ +/* post a message once, outside of perform routine */ + post("%s", x->x_msg); +} + +static void *udpreceive_tilde_new(t_floatarg fportno, t_floatarg outlets, t_floatarg blocksize) +{ + t_udpreceive_tilde *x; + int i; + + if (fportno == 0) fportno = DEFAULT_PORT; + + x = (t_udpreceive_tilde *)pd_new(udpreceive_tilde_class); + if (x) + { + for (i = sizeof(t_object); i < (int)sizeof(t_udpreceive_tilde); i++) + ((char *)x)[i] = 0; + + if ((int)outlets < 1 || (int)outlets > DEFAULT_AUDIO_CHANNELS) + { + error("udpreceive~: Number of channels must be between 1 and %d", DEFAULT_AUDIO_CHANNELS); + return NULL; + } + + x->x_noutlets = (int)outlets + 1; // extra outlet for valid flag + for (i = 0; i < x->x_noutlets; i++) + outlet_new(&x->x_obj, &s_signal); + x->x_outlet2 = outlet_new(&x->x_obj, &s_anything); + x->x_addrout = outlet_new(&x->x_obj, &s_list); + for (i = 0; i < 5; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + x->x_addr = 0; + x->x_port = 0; + x->x_myvec = (t_int **)t_getbytes(sizeof(t_int *) * (x->x_noutlets + 3)); + if (!x->x_myvec) + { + error("udpreceive~: out of memory"); + return NULL; + } + + x->x_connectsocket = x->x_socket = -1; + x->x_nconnections = x->x_underflow = x->x_overflow = 0; + x->x_hostname = ps_nothing; +/* allocate space for 16 frames of 1024 X numchannels floats*/ + for (i = 0; i < DEFAULT_AUDIO_BUFFER_FRAMES; i++) + { + x->x_frames[i].data = (char *)t_getbytes(DEFAULT_AUDIO_BUFFER_SIZE * (x->x_noutlets-1) * sizeof(t_float)); + } + x->x_clock = clock_new(&x->x_obj.ob_pd, (t_method)udpreceive_tilde_tick); + + x->x_sync = 1; + x->x_tag_errors = x->x_framein = x->x_frameout = x->x_valid = 0; + x->x_maxframes = DEFAULT_QUEUE_LENGTH; + x->x_vecsize = 64; /* we'll update this later */ + if (blocksize == 0) x->x_blocksize = DEFAULT_AUDIO_BUFFER_SIZE; + else if (DEFAULT_AUDIO_BUFFER_SIZE%(int)blocksize) + { + error("udpreceive~: blocksize must fit snugly in %d", DEFAULT_AUDIO_BUFFER_SIZE); + return NULL; + } + else x->x_blocksize = (int)blocksize; //DEFAULT_AUDIO_BUFFER_SIZE; /* <-- the only place blocksize is set */ + x->x_blockssincerecv = 0; + x->x_blocksperrecv = x->x_blocksize / x->x_vecsize; + x->x_buffering = 1; + + if (!udpreceive_tilde_createsocket(x, (int)fportno)) + { + error("udpreceive~: failed to create listening socket"); + return (NULL); + } + } + return (x); +} + +static void udpreceive_tilde_free(t_udpreceive_tilde *x) +{ + int i; + + if (x->x_connectsocket != -1) + { + sys_rmpollfn(x->x_connectsocket); + CLOSESOCKET(x->x_connectsocket); + } + if (x->x_socket != -1) + { + sys_rmpollfn(x->x_socket); + CLOSESOCKET(x->x_socket); + } + + /* free memory */ + t_freebytes(x->x_myvec, sizeof(t_int *) * (x->x_noutlets + 3)); + for (i = 0; i < DEFAULT_AUDIO_BUFFER_FRAMES; i++) + { + t_freebytes(x->x_frames[i].data, DEFAULT_AUDIO_BUFFER_SIZE * (x->x_noutlets-1) * sizeof(t_float)); + } + clock_free(x->x_clock); +} + +void udpreceive_tilde_setup(void) +{ + udpreceive_tilde_class = class_new(gensym("udpreceive~"), + (t_newmethod) udpreceive_tilde_new, (t_method) udpreceive_tilde_free, + sizeof(t_udpreceive_tilde), 0, A_DEFFLOAT, A_DEFFLOAT, A_DEFFLOAT, A_NULL); + + class_addmethod(udpreceive_tilde_class, nullfn, gensym("signal"), 0); + class_addmethod(udpreceive_tilde_class, (t_method)udpreceive_tilde_info, gensym("info"), 0); + class_addmethod(udpreceive_tilde_class, (t_method)udpreceive_tilde_dsp, gensym("dsp"), 0); + class_addmethod(udpreceive_tilde_class, (t_method)udpreceive_tilde_reset, gensym("reset"), A_DEFFLOAT, 0); + class_addmethod(udpreceive_tilde_class, (t_method)udpreceive_tilde_reset, gensym("buffer"), A_DEFFLOAT, 0); + post("udpreceive~ v%s, (c) 2004 Olaf Matthes, 2010 Martin Peach", VERSION); + + ps_format = gensym("format"); + ps_tag_errors = gensym("tag_errors"); + ps_channels = gensym("channels"); + ps_framesize = gensym("framesize"); + ps_bitrate = gensym("bitrate"); + ps_overflow = gensym("overflow"); + ps_underflow = gensym("underflow"); + ps_queuesize = gensym("queuesize"); + ps_average = gensym("average"); + ps_packets = gensym("packets"); + ps_hostname = gensym("ipaddr"); + ps_sf_float = gensym("_float_"); + ps_sf_16bit = gensym("_16bit_"); + ps_sf_8bit = gensym("_8bit_"); + ps_sf_mp3 = gensym("_mp3_"); + ps_sf_aac = gensym("_aac_"); + ps_sf_unknown = gensym("_unknown_"); + ps_nothing = gensym(""); +} + +/* error handlers */ +static int udpreceive_tilde_sockerror(char *s) +{ +#ifdef _WIN32 + int err = WSAGetLastError(); + if (err == 10054) return 1; + else if (err == 10040) post("udpreceive~: %s: message too long (%d)", s, err); + else if (err == 10053) post("udpreceive~: %s: software caused connection abort (%d)", s, err); + else if (err == 10055) post("udpreceive~: %s: no buffer space available (%d)", s, err); + else if (err == 10060) post("udpreceive~: %s: connection timed out (%d)", s, err); + else if (err == 10061) post("udpreceive~: %s: connection refused (%d)", s, err); + else post("udpreceive~: %s: %s (%d)", s, strerror(err), err); +#else + int err = errno; + post("udpreceive~: %s: %s (%d)", s, strerror(err), err); +#endif +#ifdef _WIN32 + if (err == WSAEWOULDBLOCK) +#endif +#if defined(UNIX) || defined(unix) + if (err == EAGAIN) +#endif + { + return 0; /* recoverable error */ + } + return err; /* indicate non-recoverable error */ +} + +static int udpreceive_tilde_setsocketoptions(int sockfd) +{ + int sockopt = 1; + if (setsockopt(sockfd, SOL_IP, TCP_NODELAY, (const char*)&sockopt, sizeof(int)) < 0) + post("udpreceive~: setsockopt NODELAY failed"); + + sockopt = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&sockopt, sizeof(int)) < 0) + post("udpreceive~: setsockopt REUSEADDR failed"); + return 0; +} + +/* fin udpreceive~.c */ diff --git a/udpsend-help.pd b/udpsend-help.pd new file mode 100644 index 0000000..0114f07 --- /dev/null +++ b/udpsend-help.pd @@ -0,0 +1,31 @@ +#N canvas 609 110 590 348 12; +#X msg 72 182 disconnect; +#X msg 16 59 connect 127.0.0.1 9997; +#X obj 16 306 tgl 15 0 empty empty connected 20 7 0 8 -24198 -241291 +-1 1 1; +#X text 220 60 <--first; +#X msg 25 81 send 0 1 2 3; +#X text 8 5 udpsend sends bytes over a udp connection.; +#X text 8 28 Used in conjunction with packOSC will send OSC over udp +; +#X obj 16 283 udpsend; +#X msg 32 103 send ../doc/5.reference/test.txt; +#X obj 387 151 openpanel; +#X msg 387 175 send \$1; +#X obj 387 132 bng 15 250 50 0 empty empty empty 17 7 0 10 -24198 -241291 +-1; +#X text 327 104 send a file; +#X text 141 81 send raw data; +#X text 410 130 ...any file; +#X msg 40 126 99 98 97; +#X text 361 311 Martin Peach 2007/06/20; +#X text 120 126 'send' prefix is optional; +#X connect 0 0 7 0; +#X connect 1 0 7 0; +#X connect 4 0 7 0; +#X connect 7 0 2 0; +#X connect 8 0 7 0; +#X connect 9 0 10 0; +#X connect 10 0 7 0; +#X connect 11 0 9 0; +#X connect 15 0 7 0; diff --git a/udpsend.c b/udpsend.c new file mode 100644 index 0000000..98db672 --- /dev/null +++ b/udpsend.c @@ -0,0 +1,257 @@ +/* udpsend.c 20060424. Martin Peach did it based on x_net.c. x_net.c header follows: */ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* network */ + +#include "m_pd.h" +#include "s_stuff.h" + +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#endif + +static t_class *udpsend_class; + +typedef struct _udpsend +{ + t_object x_obj; + int x_fd; +} t_udpsend; + +void udpsend_setup(void); +static void udpsend_free(t_udpsend *x); +static void udpsend_send(t_udpsend *x, t_symbol *s, int argc, t_atom *argv); +static void udpsend_disconnect(t_udpsend *x); +static void udpsend_connect(t_udpsend *x, t_symbol *hostname, t_floatarg fportno); +static void *udpsend_new(void); + +static void *udpsend_new(void) +{ + t_udpsend *x = (t_udpsend *)pd_new(udpsend_class); + outlet_new(&x->x_obj, &s_float); + x->x_fd = -1; + return (x); +} + +static void udpsend_connect(t_udpsend *x, t_symbol *hostname, + t_floatarg fportno) +{ + struct sockaddr_in server; + struct hostent *hp; + int sockfd; + int portno = fportno; + int broadcast = 1;/* nonzero is true */ + + if (x->x_fd >= 0) + { + error("udpsend: already connected"); + return; + } + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_DGRAM, 0); +#ifdef DEBUG + fprintf(stderr, "udpsend_connect: send socket %d\n", sockfd); +#endif + if (sockfd < 0) + { + sys_sockerror("udpsend: socket"); + return; + } +/* Based on zmoelnig's patch 2221504: +Enable sending of broadcast messages (if hostname is a broadcast address)*/ +#ifdef SO_BROADCAST + if( 0 != setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (const void *)&broadcast, sizeof(broadcast))) + { + pd_error(x, "couldn't switch to broadcast mode"); + } +#endif /* SO_BROADCAST */ + + /* connect socket using hostname provided in command line */ + server.sin_family = AF_INET; + hp = gethostbyname(hostname->s_name); + if (hp == 0) + { + post("udpsend: bad host?\n"); + return; + } + memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); + + /* assign client port number */ + server.sin_port = htons((u_short)portno); + + post("udpsend: connecting to port %d", portno); + /* try to connect. */ + if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) + { + sys_sockerror("udpsend: connecting stream socket"); + sys_closesocket(sockfd); + return; + } + x->x_fd = sockfd; + outlet_float(x->x_obj.ob_outlet, 1); +} + +static void udpsend_disconnect(t_udpsend *x) +{ + if (x->x_fd >= 0) + { + sys_closesocket(x->x_fd); + x->x_fd = -1; + outlet_float(x->x_obj.ob_outlet, 0); + } +} + +static void udpsend_send(t_udpsend *x, t_symbol *s, int argc, t_atom *argv) +{ +#define BYTE_BUF_LEN 65536 // arbitrary maximum similar to max IP packet size + static char byte_buf[BYTE_BUF_LEN]; + int d; + int i, j; + unsigned char c; + float f, e; + char *bp; + int length, sent; + int result; + static double lastwarntime; + static double pleasewarn; + double timebefore; + double timeafter; + int late; + char fpath[FILENAME_MAX]; + FILE *fptr; + +#ifdef DEBUG + post("s: %s", s->s_name); + post("argc: %d", argc); +#endif + for (i = j = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { + f = argv[i].a_w.w_float; + d = (int)f; + e = f - d; + if (e != 0) + { + error("udpsend_send: item %d (%f) is not an integer", i, f); + return; + } + c = (unsigned char)d; + if (c != d) + { + error("udpsend_send: item %d (%f) is not between 0 and 255", i, f); + return; + } +#ifdef DEBUG + post("udpsend_send: argv[%d]: %d", i, c); +#endif + byte_buf[j++] = c; + } + else if (argv[i].a_type == A_SYMBOL) + { + + atom_string(&argv[i], fpath, FILENAME_MAX); +#ifdef DEBUG + post ("udpsend fname: %s", fpath); +#endif + fptr = fopen(fpath, "rb"); + if (fptr == NULL) + { + post("udpsend: unable to open \"%s\"", fpath); + return; + } + rewind(fptr); +#ifdef DEBUG + post("udpsend: d is %d", d); +#endif + while ((d = fgetc(fptr)) != EOF) + { + byte_buf[j++] = (char)(d & 0x0FF); +#ifdef DEBUG + post("udpsend: byte_buf[%d] = %d", j-1, byte_buf[j-1]); +#endif + if (j >= BYTE_BUF_LEN) + { + post ("udpsend: file too long, truncating at %lu", BYTE_BUF_LEN); + break; + } + } + fclose(fptr); + fptr = NULL; + post("udpsend: read \"%s\" length %d byte%s", fpath, j, ((d==1)?"":"s")); + } + else + { + error("udpsend_send: item %d is not a float or a file name", i); + return; + } + } + + length = j; + if ((x->x_fd >= 0) && (length > 0)) + { + for (bp = byte_buf, sent = 0; sent < length;) + { + timebefore = sys_getrealtime(); + result = send(x->x_fd, byte_buf, length-sent, 0); + timeafter = sys_getrealtime(); + late = (timeafter - timebefore > 0.005); + if (late || pleasewarn) + { + if (timeafter > lastwarntime + 2) + { + post("udpsend blocked %d msec", + (int)(1000 * ((timeafter - timebefore) + pleasewarn))); + pleasewarn = 0; + lastwarntime = timeafter; + } + else if (late) pleasewarn += timeafter - timebefore; + } + if (result <= 0) + { + sys_sockerror("udpsend"); + udpsend_disconnect(x); + break; + } + else + { + sent += result; + bp += result; + } + } + } + else error("udpsend: not connected"); +} + +static void udpsend_free(t_udpsend *x) +{ + udpsend_disconnect(x); +} + +void udpsend_setup(void) +{ + udpsend_class = class_new(gensym("udpsend"), (t_newmethod)udpsend_new, + (t_method)udpsend_free, + sizeof(t_udpsend), 0, 0); + class_addmethod(udpsend_class, (t_method)udpsend_connect, + gensym("connect"), A_SYMBOL, A_FLOAT, 0); + class_addmethod(udpsend_class, (t_method)udpsend_disconnect, + gensym("disconnect"), 0); + class_addmethod(udpsend_class, (t_method)udpsend_send, gensym("send"), + A_GIMME, 0); + class_addlist(udpsend_class, (t_method)udpsend_send); +} + +/* end udpsend.c*/ + diff --git a/udpsend~-help.pd b/udpsend~-help.pd new file mode 100644 index 0000000..bd03c81 --- /dev/null +++ b/udpsend~-help.pd @@ -0,0 +1,173 @@ +#N canvas 197 27 785 803 10; +#X obj -178 225 osc~ 440; +#X msg -398 60 disconnect; +#X msg -353 105 format float; +#X msg -332 126 format 16bit; +#X msg -311 147 format 8bit; +#X msg -440 18 connect localhost 8008; +#X text -244 103 float is the most expensive with the best resolution +(32bit) \, default is 16bit; +#X msg -419 39 connect 255.255.255.255 8008; +#X obj 52 687 print udpreceive~; +#X obj 224 369 print udpsend~; +#X obj -178 320 tgl 15 0 empty empty empty 17 7 0 10 -4034 -1 -1 0 +1; +#X symbolatom -97 435 10 0 0 0 - - -; +#X floatatom -44 369 5 0 0 0 - - -; +#X floatatom 10 391 9 0 0 0 - - -; +#X floatatom 63 411 9 0 0 0 - - -; +#X symbolatom 117 390 10 0 0 0 - - -; +#X obj -97 411 prepend set; +#X obj 117 367 prepend set; +#X text -102 368 channels:; +#X text -144 442 format:; +#X text 13 411 bitrate:; +#X text -51 391 framesize:; +#X text 98 389 to:; +#X msg -216 242 info; +#X symbolatom -293 774 10 0 0 0 - - -; +#X floatatom -259 708 5 0 0 0 - - -; +#X floatatom -224 687 7 0 0 0 - - -; +#X floatatom -190 752 9 0 0 0 - - -; +#X obj -293 733 prepend set; +#X text -323 707 channels:; +#X text -339 773 format:; +#X text -239 751 bitrate:; +#X text -288 686 framesize:; +#X floatatom -155 709 9 0 0 0 - - -; +#X floatatom -121 687 9 0 0 0 - - -; +#X floatatom -86 730 5 0 0 0 - - -; +#X floatatom -52 709 5 0 0 0 - - -; +#X text -212 708 overflow:; +#X text -181 686 underflow:; +#X text -147 729 queuesize:; +#X text -100 708 average:; +#X msg -435 478 info; +#X text -237 34 broadcast to everybody on your local subnet listening +on the specified port; +#X msg -455 458 reset; +#X text -402 477 status info to rightmost outlet; +#X text -415 241 status info to rightmost outlet; +#X text -417 457 reset underflow & overflow counters; +#X floatatom -260 589 3 0 0 0 - - -; +#X floatatom -237 589 3 0 0 0 - - -; +#X floatatom -214 589 3 0 0 0 - - -; +#X floatatom -191 589 3 0 0 0 - - -; +#X floatatom -167 589 5 0 0 0 - - -; +#X obj -260 563 unpack 0 0 0 0 0; +#X text -297 588 from:; +#X obj -179 268 *~; +#X floatatom -164 150 5 0 0 0 - - -; +#X text -66 148 Framesize = (blocksize) X (number of channels) X (bytes +per sample); +#X obj -97 339 route format channels framesize bitrate ipaddr vecsize +; +#X floatatom 170 430 9 0 0 0 - - -; +#X text 70 430 dsp vector size:; +#X msg -258 200 channels \$1; +#X obj -412 185 hradio 15 1 0 4 empty empty empty 0 -8 0 10 -4034 -1 +-1 0; +#X obj -178 297 udpsend~ 2 512; +#X text -88 296 sends 2 dsp-channels using 512-sample blocks; +#X obj -389 541 udpreceive~ 8008 2 512; +#X obj -388 572 dac~ 1 2; +#X text 68 448 (blocksize must be a multiple of this); +#X text -160 318 1 = transmitting; +#X obj -97 246 noise~; +#X obj -98 268 *~; +#X obj -164 170 / 100; +#X text -455 338 Based on: [netreceive~] and [netsend~]by Olaf Matthes +; +#X floatatom -17 752 9 0 0 0 - - -; +#X text -69 751 packets:; +#X text -50 177 Default blocksize is 2048 The number of samples per +block must be an integer multiple of the number of samples in one signal +vector.; +#X text -28 225 Arguments: (1st required \, 2nd optional) 1:number +of channels to send. 2:blocksize = number of samples per channel per +frame. (Blocksize of sender and receiver must be the same.); +#X text -127 554 To communicate \, a [udpreceive~] and [udpsend~] pair +must have the same number of channels and the same blocksize. Also +[udpsend~] must [connect( to the port on which [udpreceive~] is listening. +; +#X text -329 59 stop transmitting; +#X text -355 76 format defines the resolution of the sent signal and +may be changed on-the-fly; +#X text -450 200 number of channels to transmit; +#X msg -414 499 buffer 2; +#X text -352 498 set number of frames to buffer before playback; +#X text -458 365 [udpsend~] transmits dsp vectors ("audio") via UDP. +UDP is a connectionless protocol \, so [udpsend~] will transmit even +if nothing is receiving.; +#X text -299 18 connect to and begin transmitting +; +#X text -456 313 [udpreceive~] and [udpsend~]; +#X text -248 540 receives 2 channels on port 8008 Same blocksize as +udpsend~; +#X floatatom -412 216 5 0 0 0 - - -; +#X obj -288 172 tgl 15 0 empty empty toggle_connection 17 7 0 10 -4034 +-1 -1 0 1; +#X obj -293 650 route format channels framesize bitrate overflow underflow +queuesize average packets tag_errors; +#X floatatom 17 730 9 0 0 0 - - -; +#X text -49 729 tag errors:; +#X obj -326 601 env~; +#X floatatom -326 624 9 0 0 0 - - -; +#X text -266 613 The rightmost signal outlet outputs 1 if the stream +is valid \, else 0; +#X text -455 326 Author: Martin Peach 2010/03/22; +#X connect 0 0 54 0; +#X connect 1 0 62 0; +#X connect 2 0 62 0; +#X connect 3 0 62 0; +#X connect 4 0 62 0; +#X connect 5 0 62 0; +#X connect 7 0 62 0; +#X connect 16 0 11 0; +#X connect 17 0 15 0; +#X connect 23 0 62 0; +#X connect 28 0 24 0; +#X connect 41 0 64 0; +#X connect 43 0 64 0; +#X connect 52 0 47 0; +#X connect 52 1 48 0; +#X connect 52 2 49 0; +#X connect 52 3 50 0; +#X connect 52 4 51 0; +#X connect 54 0 62 0; +#X connect 55 0 70 0; +#X connect 57 0 16 0; +#X connect 57 1 12 0; +#X connect 57 2 13 0; +#X connect 57 3 14 0; +#X connect 57 4 17 0; +#X connect 57 5 58 0; +#X connect 57 6 9 0; +#X connect 60 0 62 0; +#X connect 61 0 60 0; +#X connect 61 0 86 0; +#X connect 62 0 10 0; +#X connect 62 1 57 0; +#X connect 64 0 65 0; +#X connect 64 1 65 1; +#X connect 64 2 91 0; +#X connect 64 3 88 0; +#X connect 64 4 52 0; +#X connect 68 0 69 0; +#X connect 69 0 62 1; +#X connect 70 0 54 1; +#X connect 70 0 69 1; +#X connect 80 0 64 0; +#X connect 87 0 62 0; +#X connect 88 0 28 0; +#X connect 88 1 25 0; +#X connect 88 2 26 0; +#X connect 88 3 27 0; +#X connect 88 4 33 0; +#X connect 88 5 34 0; +#X connect 88 6 35 0; +#X connect 88 7 36 0; +#X connect 88 8 72 0; +#X connect 88 9 89 0; +#X connect 88 10 8 0; +#X connect 91 0 92 0; diff --git a/udpsend~.c b/udpsend~.c new file mode 100644 index 0000000..aa556c7 --- /dev/null +++ b/udpsend~.c @@ -0,0 +1,701 @@ +/* udpsend~ started by Martin Peach on 20100110, based on netsend~ */ +/* udpsend~ sends audio via udp only.*/ +/* It is a PD external, all Max stuff has been removed from the source */ +/* ------------------------ netsend~ ------------------------------------------ */ +/* */ +/* Tilde object to send uncompressed audio data to netreceive~. */ +/* Written by Olaf Matthes . */ +/* Based on streamout~ by Guenter Geiger. */ +/* Get source at http://www.akustische-kunst.org/ */ +/* */ +/* 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. */ +/* */ +/* See file LICENSE for further informations on licensing terms. */ +/* */ +/* 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, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* This project was commissioned by the Society for Arts and Technology [SAT], */ +/* Montreal, Quebec, Canada, http://www.sat.qc.ca/. */ +/* */ +/* ---------------------------------------------------------------------------- */ + +#include "m_pd.h" + +#include "udpsend~.h" + +#include +#include +#include +#include +#if defined(UNIX) || defined(unix) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SOCKET_ERROR -1 +#endif +#ifdef _WIN32 +#include +#include "pthread.h" +#endif + +#ifdef MSG_NOSIGNAL +#define SEND_FLAGS /*MSG_DONTWAIT|*/MSG_NOSIGNAL +#else +#define SEND_FLAGS 0 +#endif + +#ifndef SOL_IP +#define SOL_IP IPPROTO_IP +#endif + + +/* ------------------------ udpsend~ ----------------------------- */ + +static t_class *udpsend_tilde_class; + +static t_symbol *ps_nothing, *ps_localhost, *ps_vecsize; +static t_symbol *ps_format, *ps_channels, *ps_framesize; +static t_symbol *ps_sf_float, *ps_sf_16bit, *ps_sf_8bit; +static t_symbol *ps_sf_unknown, *ps_bitrate, *ps_hostname; + +typedef struct _udpsend_tilde +{ + t_object x_obj; + t_outlet *x_outlet; + t_outlet *x_outlet2; + t_clock *x_clock; + int x_fd; + t_tag x_tag; + t_symbol* x_hostname; + int x_portno; + int x_connectstate; + char *x_cbuf; + int x_cbufsize; + int x_blocksize; /* set to DEFAULT_AUDIO_BUFFER_SIZE or user-supplied argument 3 in udpsend_tilde_new() */ + int x_blockspersend; /* set to x->x_blocksize / x->x_vecsize in udpsend_tilde_perform() */ + int x_blockssincesend; + + long x_samplerate; /* samplerate we're running at */ + int x_vecsize; /* current DSP signal vector size */ + int x_ninlets; /* number of inlets */ + int x_channels; /* number of channels we want to stream */ + int x_format; /* format of streamed audio data */ + int x_count; /* total number of audio frames */ + t_int **x_myvec; /* vector we pass on in the DSP routine */ + + pthread_mutex_t x_mutex; + pthread_t x_childthread; /* a thread to initiate a connection to the remote port */ + pthread_attr_t x_childthread_attr; /* child thread should have detached attribute so it can be cleaned up after it exits. */ + int x_childthread_result; /* result from pthread_create. Zero if x_childthread represents a valid thread. */ +} t_udpsend_tilde; + +#define NO_CHILDTHREAD 1 /* not zero */ + +/* function prototypes */ +static int udpsend_tilde_sockerror(char *s); +static void udpsend_tilde_closesocket(int fd); +static void udpsend_tilde_notify(t_udpsend_tilde *x); +static void udpsend_tilde_disconnect(t_udpsend_tilde *x); +static void *udpsend_tilde_doconnect(void *zz); +static void udpsend_tilde_connect(t_udpsend_tilde *x, t_symbol *host, t_floatarg fportno); +static t_int *udpsend_tilde_perform(t_int *w); +static void udpsend_tilde_dsp(t_udpsend_tilde *x, t_signal **sp); +static void udpsend_tilde_channels(t_udpsend_tilde *x, t_floatarg channels); +static void udpsend_tilde_format(t_udpsend_tilde *x, t_symbol* form, t_floatarg bitrate); +static void udpsend_tilde_float(t_udpsend_tilde* x, t_floatarg arg); +static void udpsend_tilde_info(t_udpsend_tilde *x); +static void *udpsend_tilde_new(t_floatarg inlets, t_floatarg blocksize); +static void udpsend_tilde_free(t_udpsend_tilde* x); +void udpsend_tilde_setup(void); + +/* functions */ +static void udpsend_tilde_notify(t_udpsend_tilde *x) +{ + pthread_mutex_lock(&x->x_mutex); + x->x_childthread_result = NO_CHILDTHREAD; /* connection thread has ended */ + outlet_float(x->x_outlet, x->x_connectstate); /* we should be connected */ + pthread_mutex_unlock(&x->x_mutex); +} + +static void udpsend_tilde_disconnect(t_udpsend_tilde *x) +{ + pthread_mutex_lock(&x->x_mutex); + if (x->x_fd != -1) + { + udpsend_tilde_closesocket(x->x_fd); + x->x_fd = -1; + x->x_connectstate = 0; + outlet_float(x->x_outlet, 0); + } + pthread_mutex_unlock(&x->x_mutex); +} + +/* udpsend_tilde_doconnect runs in the child thread, which terminates as soon as a connection is */ +static void *udpsend_tilde_doconnect(void *zz) +{ + t_udpsend_tilde *x = (t_udpsend_tilde *)zz; + struct sockaddr_in server; + struct hostent *hp; + int intarg = 1; + int sockfd; + int portno; + int broadcast = 1;/* nonzero is true */ + t_symbol *hostname; + + pthread_mutex_lock(&x->x_mutex); + hostname = x->x_hostname; + portno = x->x_portno; + pthread_mutex_unlock(&x->x_mutex); + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) + { + post("udpsend~: connection to %s on port %d failed", hostname->s_name,portno); + udpsend_tilde_sockerror("socket"); + x->x_childthread_result = NO_CHILDTHREAD; + return (0); + } + +#ifdef SO_BROADCAST + if( 0 != setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (const void *)&broadcast, sizeof(broadcast))) + { + udpsend_tilde_sockerror("setting SO_BROADCAST"); + } +#endif /* SO_BROADCAST */ + + /* connect socket using hostname provided in command line */ + server.sin_family = AF_INET; + hp = gethostbyname(x->x_hostname->s_name); + if (hp == 0) + { + post("udpsend~: bad host?"); + x->x_childthread_result = NO_CHILDTHREAD; + return (0); + } + +#ifdef SO_PRIORITY + /* set high priority, LINUX only */ + intarg = 6; /* select a priority between 0 and 7 */ + if (setsockopt(sockfd, SOL_SOCKET, SO_PRIORITY, (const char*)&intarg, sizeof(int)) < 0) + { + udpsend_tilde_sockerror("setting SO_PRIORITY"); + } +#endif + + memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); + + /* assign client port number */ + server.sin_port = htons((unsigned short)portno); + + /* try to connect */ + if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) + { + udpsend_tilde_sockerror("connecting stream socket"); + udpsend_tilde_closesocket(sockfd); + x->x_childthread_result = NO_CHILDTHREAD; + return (0); + } + + post("udpsend~: connected host %s on port %d", hostname->s_name, portno); + + pthread_mutex_lock(&x->x_mutex); + x->x_fd = sockfd; + x->x_connectstate = 1; + clock_delay(x->x_clock, 0);/* udpsend_tilde_notify is called in next clock tick */ + pthread_mutex_unlock(&x->x_mutex); + return (0); +} + +static void udpsend_tilde_connect(t_udpsend_tilde *x, t_symbol *host, t_floatarg fportno) +{ + pthread_mutex_lock(&x->x_mutex); + if (x->x_childthread_result == 0) + { + pthread_mutex_unlock(&x->x_mutex); + post("udpsend~: already trying to connect"); + return; + } + if (x->x_fd != -1) + { + pthread_mutex_unlock(&x->x_mutex); + post("udpsend~: already connected"); + return; + } + + if (host != ps_nothing) + x->x_hostname = host; + else + x->x_hostname = ps_localhost; /* default host */ + + if (!fportno) + x->x_portno = DEFAULT_PORT; + else + x->x_portno = (int)fportno; + x->x_count = 0; + + /* start child thread to connect */ + /* sender thread should start out detached so its resouces will be freed when it is done */ + if (0!= (x->x_childthread_result = pthread_attr_init(&x->x_childthread_attr))) + { + pthread_mutex_unlock(&x->x_mutex); + post("udpsend~: pthread_attr_init failed: %d", x->x_childthread_result); + return; + } + if(0!= (x->x_childthread_result = pthread_attr_setdetachstate(&x->x_childthread_attr, PTHREAD_CREATE_DETACHED))) + { + pthread_mutex_unlock(&x->x_mutex); + post("udpsend~: pthread_attr_setdetachstate failed: %d", x->x_childthread_result); + return; + } + if (0 != (x->x_childthread_result = pthread_create(&x->x_childthread, &x->x_childthread_attr, udpsend_tilde_doconnect, x))) + { + pthread_mutex_unlock(&x->x_mutex); + post("udpsend~: couldn't create sender thread (%d)", x->x_childthread_result); + return; + } + pthread_mutex_unlock(&x->x_mutex); +} + +static t_int *udpsend_tilde_perform(t_int *w) +{ + t_udpsend_tilde* x = (t_udpsend_tilde*) (w[1]); + int n = (int)(w[2]); + t_float *in[DEFAULT_AUDIO_CHANNELS]; + const int offset = 3; + char* bp = NULL; + int i, length = x->x_blocksize * SF_SIZEOF(x->x_tag.format) * x->x_tag.channels; + int sent = 0; + + pthread_mutex_lock(&x->x_mutex); + + for (i = 0; i < x->x_ninlets; i++) + in[i] = (t_float *)(w[offset + i]); + + if (n != x->x_vecsize) /* resize buffer */ + { + x->x_vecsize = n; + x->x_blockspersend = x->x_blocksize / x->x_vecsize; + x->x_blockssincesend = 0; + length = x->x_blocksize * SF_SIZEOF(x->x_tag.format) * x->x_tag.channels; + } + + /* format the buffer */ + switch (x->x_tag.format) + { + case SF_FLOAT: + { + int32_t* fbuf = (int32_t *)x->x_cbuf + (x->x_blockssincesend * x->x_vecsize * x->x_tag.channels); + flint fl; + + while (n--) + for (i = 0; i < x->x_tag.channels; i++) + { + fl.f32 = *(in[i]++); + *fbuf++ = htonl(fl.i32); + } + break; + } + case SF_16BIT: + { + short* cibuf = (short *)x->x_cbuf + (x->x_blockssincesend * x->x_vecsize * x->x_tag.channels); + + while (n--) + for (i = 0; i < x->x_tag.channels; i++) + *cibuf++ = htons((short)floor(32767.5 * *(in[i]++)));/* signed binary */ + break; + } + case SF_8BIT: + { + unsigned char* cbuf = (unsigned char*)x->x_cbuf + (x->x_blockssincesend * x->x_vecsize * x->x_tag.channels); + + while (n--) + for (i = 0; i < x->x_tag.channels; i++) + *cbuf++ = (unsigned char)floor(128. * (1.0 + *(in[i]++))); /* offset binary */ + break; + } + default: + break; + } + + if (!(x->x_blockssincesend < x->x_blockspersend - 1)) /* time to send the buffer */ + { + x->x_blockssincesend = 0; + x->x_count++; /* count data packet we're going to send */ + + if (x->x_fd != -1) + { + bp = (char *)x->x_cbuf; + /* fill in the header tag */ + x->x_tag.tag[0] = 'T'; + x->x_tag.tag[1] = 'A'; + x->x_tag.tag[2] = 'G'; + x->x_tag.tag[3] = '!'; + x->x_tag.framesize = htonl(length); + x->x_tag.count = htonl(x->x_count); + /* send the format tag */ + if (send(x->x_fd, (char*)&x->x_tag, sizeof(t_tag), SEND_FLAGS) < 0) + { + udpsend_tilde_sockerror("send tag"); + pthread_mutex_unlock(&x->x_mutex); + udpsend_tilde_disconnect(x); + return (w + offset + x->x_ninlets); + } + if (length != 0) +/* UDP: max. packet size is 64k (incl. headers) so we have to split */ + { +#ifdef __APPLE__ + /* WARNING: due to a 'bug' (maybe Apple would call it a feature?) in OS X + send calls with data packets larger than 16k fail with error number 40! + Thus we have to split the data packets into several packets that are + 16k in size. The other side will reassemble them again. */ + int size = DEFAULT_UDP_PACKT_SIZE; + if (length < size) /* maybe data fits into one packet? */ + size = length; + /* send the buffer */ + for (sent = 0; sent < length;) + { + int ret = 0; + ret = send(x->x_fd, bp, size, SEND_FLAGS); + if (ret <= 0) + { + udpsend_tilde_sockerror("send data"); + pthread_mutex_unlock(&x->x_mutex); + udpsend_tilde_disconnect(x); + return (w + offset + x->x_ninlets); + } + else + { + bp += ret; + sent += ret; + if ((length - sent) < size) + size = length - sent; + } + } +#else + /* If there is any data, send the buffer, the OS might segment it into smaller packets */ + int ret = send(x->x_fd, bp, length, SEND_FLAGS); + if (ret <= 0) + { + post ("udpsend~: sending length %ld", length); + udpsend_tilde_sockerror("send data"); + pthread_mutex_unlock(&x->x_mutex); + udpsend_tilde_disconnect(x); + return (w + offset + x->x_ninlets); + } +#endif + } + } + +/* check whether user has updated any parameters */ + if (x->x_tag.channels != x->x_channels) + { + x->x_tag.channels = x->x_channels; + } + if (x->x_tag.format != x->x_format) + { + x->x_tag.format = x->x_format; + } + } + else + { + x->x_blockssincesend++; + } + pthread_mutex_unlock(&x->x_mutex); + return (w + offset + x->x_ninlets); +} + +static void udpsend_tilde_dsp(t_udpsend_tilde *x, t_signal **sp) +{ + int i; + + pthread_mutex_lock(&x->x_mutex); + + x->x_myvec[0] = (t_int*)x; + x->x_myvec[1] = (t_int*)sp[0]->s_n; + + x->x_samplerate = sp[0]->s_sr; + + for (i = 0; i < x->x_ninlets; i++) + { + x->x_myvec[2 + i] = (t_int*)sp[i]->s_vec; + } + + pthread_mutex_unlock(&x->x_mutex); + + if (DEFAULT_AUDIO_BUFFER_SIZE % sp[0]->s_n) + { + error("udpsend~: signal vector size too large (needs to be even divisor of %d)", DEFAULT_AUDIO_BUFFER_SIZE); + } + else + { + dsp_addv(udpsend_tilde_perform, x->x_ninlets + 2, (t_int*)x->x_myvec); + } +} + +static void udpsend_tilde_channels(t_udpsend_tilde *x, t_floatarg channels) +{ + pthread_mutex_lock(&x->x_mutex); + if (channels >= 0 && channels <= x->x_ninlets) + { + x->x_channels = (int)channels; + post("udpsend~: channels set to %d", (int)channels); + } + else post ("udpsend~ number of channels must be between 0 and %d", x->x_ninlets); + pthread_mutex_unlock(&x->x_mutex); +} + +static void udpsend_tilde_format(t_udpsend_tilde *x, t_symbol* form, t_floatarg bitrate) +{ + pthread_mutex_lock(&x->x_mutex); + if (!strncmp(form->s_name,"float", 5) && x->x_tag.format != SF_FLOAT) + { + x->x_format = (int)SF_FLOAT; + } + else if (!strncmp(form->s_name,"16bit", 5) && x->x_tag.format != SF_16BIT) + { + x->x_format = (int)SF_16BIT; + } + else if (!strncmp(form->s_name,"8bit", 4) && x->x_tag.format != SF_8BIT) + { + x->x_format = (int)SF_8BIT; + } + + post("udpsend~: format set to %s", form->s_name); + pthread_mutex_unlock(&x->x_mutex); +} + +static void udpsend_tilde_float(t_udpsend_tilde* x, t_floatarg arg) +{ + if (arg == 0.0) + udpsend_tilde_disconnect(x); + else + udpsend_tilde_connect(x,x->x_hostname,(float) x->x_portno); +} + +/* send stream info */ +static void udpsend_tilde_info(t_udpsend_tilde *x) +{ + t_atom list[2]; + t_symbol *sf_format; + t_float bitrate; + + bitrate = (t_float)((SF_SIZEOF(x->x_tag.format) * x->x_samplerate * 8 * x->x_tag.channels) / 1000.); + + switch (x->x_tag.format) + { + case SF_FLOAT: + { + sf_format = ps_sf_float; + break; + } + case SF_16BIT: + { + sf_format = ps_sf_16bit; + break; + } + case SF_8BIT: + { + sf_format = ps_sf_8bit; + break; + } + default: + { + sf_format = ps_sf_unknown; + break; + } + } + + /* --- stream information (t_tag) --- */ + + /* audio format */ + SETSYMBOL(list, (t_symbol *)sf_format); + outlet_anything(x->x_outlet2, ps_format, 1, list); + + /* channels */ + SETFLOAT(list, (t_float)x->x_tag.channels); + outlet_anything(x->x_outlet2, ps_channels, 1, list); + + /* current signal vector size */ + SETFLOAT(list, (t_float)x->x_vecsize); + outlet_anything(x->x_outlet2, ps_vecsize, 1, list); + + /* framesize */ + SETFLOAT(list, (t_float)(ntohl(x->x_tag.framesize))); + outlet_anything(x->x_outlet2, ps_framesize, 1, list); + + /* bitrate */ + SETFLOAT(list, (t_float)bitrate); + outlet_anything(x->x_outlet2, ps_bitrate, 1, list); + + /* IP address */ + SETSYMBOL(list, (t_symbol *)x->x_hostname); + outlet_anything(x->x_outlet2, ps_hostname, 1, list); +} + +static void *udpsend_tilde_new(t_floatarg inlets, t_floatarg blocksize) +{ + int i; + + t_udpsend_tilde *x = (t_udpsend_tilde *)pd_new(udpsend_tilde_class); + if (x) + { + for (i = sizeof(t_object); i < (int)sizeof(t_udpsend_tilde); i++) + ((char *)x)[i] = 0; + + if ((int)inlets < 1 || (int)inlets > DEFAULT_AUDIO_CHANNELS) + { + error("udpsend~: Number of channels must be between 1 and %d", DEFAULT_AUDIO_CHANNELS); + return NULL; + } + x->x_ninlets = (int)inlets; + for (i = 1; i < x->x_ninlets; i++) + inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal); + + x->x_outlet = outlet_new(&x->x_obj, &s_float); + x->x_outlet2 = outlet_new(&x->x_obj, &s_list); + x->x_clock = clock_new(x, (t_method)udpsend_tilde_notify); + + x->x_myvec = (t_int **)t_getbytes(sizeof(t_int *) * (x->x_ninlets + 3)); + if (!x->x_myvec) + { + error("udpsend~: out of memory"); + return NULL; + } + + pthread_mutex_init(&x->x_mutex, 0); + + x->x_hostname = ps_localhost; + x->x_portno = DEFAULT_PORT; + x->x_connectstate = 0; + x->x_childthread_result = NO_CHILDTHREAD; + x->x_fd = -1; + + x->x_tag.format = x->x_format = SF_FLOAT; + x->x_tag.channels = x->x_channels = x->x_ninlets; + x->x_vecsize = 64; /* this is updated in the perform routine udpsend_tilde_perform */ + x->x_cbuf = NULL; + if (blocksize == 0) x->x_blocksize = DEFAULT_AUDIO_BUFFER_SIZE; + else if (DEFAULT_AUDIO_BUFFER_SIZE%(int)blocksize) + { + error("udpsend~: blocksize must fit snugly in %d", DEFAULT_AUDIO_BUFFER_SIZE); + return NULL; + } + else x->x_blocksize = (int)blocksize; //DEFAULT_AUDIO_BUFFER_SIZE; /* <-- the only place blocksize is set */ + x->x_blockspersend = x->x_blocksize / x->x_vecsize; /* 1024/64 = 16 blocks */ + x->x_blockssincesend = 0; + x->x_cbufsize = x->x_blocksize * sizeof(t_float) * x->x_ninlets; + x->x_cbuf = (char *)t_getbytes(x->x_cbufsize); + +#if defined(UNIX) || defined(unix) + /* we don't want to get signaled in case send() fails */ + signal(SIGPIPE, SIG_IGN); +#endif + } + + return (x); +} + +static void udpsend_tilde_free(t_udpsend_tilde* x) +{ + udpsend_tilde_disconnect(x); + + /* free the memory */ + if (x->x_cbuf)t_freebytes(x->x_cbuf, x->x_cbufsize); + if (x->x_myvec)t_freebytes(x->x_myvec, sizeof(t_int) * (x->x_ninlets + 3)); + + clock_free(x->x_clock); + + pthread_mutex_destroy(&x->x_mutex); +} + +void udpsend_tilde_setup(void) +{ + udpsend_tilde_class = class_new(gensym("udpsend~"), (t_newmethod)udpsend_tilde_new, (t_method)udpsend_tilde_free, + sizeof(t_udpsend_tilde), 0, A_DEFFLOAT, A_DEFFLOAT, A_NULL); + class_addmethod(udpsend_tilde_class, nullfn, gensym("signal"), 0); + class_addmethod(udpsend_tilde_class, (t_method)udpsend_tilde_dsp, gensym("dsp"), 0); + class_addfloat(udpsend_tilde_class, udpsend_tilde_float); + class_addmethod(udpsend_tilde_class, (t_method)udpsend_tilde_info, gensym("info"), 0); + class_addmethod(udpsend_tilde_class, (t_method)udpsend_tilde_connect, gensym("connect"), A_DEFSYM, A_DEFFLOAT, 0); + class_addmethod(udpsend_tilde_class, (t_method)udpsend_tilde_disconnect, gensym("disconnect"), 0); + class_addmethod(udpsend_tilde_class, (t_method)udpsend_tilde_channels, gensym("channels"), A_FLOAT, 0); + class_addmethod(udpsend_tilde_class, (t_method)udpsend_tilde_format, gensym("format"), A_SYMBOL, A_DEFFLOAT, 0); + class_sethelpsymbol(udpsend_tilde_class, gensym("udpsend~")); + post("udpsend~ v%s, (c) 2004-2005 Olaf Matthes, 2010 Martin Peach", VERSION); + post("udpsend~ Default blocksize is %d", DEFAULT_AUDIO_BUFFER_SIZE); + + ps_nothing = gensym(""); + ps_localhost = gensym("localhost"); + ps_hostname = gensym("ipaddr"); + ps_format = gensym("format"); + ps_channels = gensym("channels"); + ps_vecsize = gensym("vecsize"); + ps_framesize = gensym("framesize"); + ps_bitrate = gensym("bitrate"); + ps_sf_float = gensym("_float_"); + ps_sf_16bit = gensym("_16bit_"); + ps_sf_8bit = gensym("_8bit_"); + ps_sf_unknown = gensym("_unknown_"); +} + +/* Utility functions */ + +static int udpsend_tilde_sockerror(char *s) +{ +#ifdef _WIN32 + int err = WSAGetLastError(); + if (err == 10054) return 1; + else if (err == 10053) post("udpsend~: %s: software caused connection abort (%d)", s, err); + else if (err == 10055) post("udpsend~: %s: no buffer space available (%d)", s, err); + else if (err == 10060) post("udpsend~: %s: connection timed out (%d)", s, err); + else if (err == 10061) post("udpsend~: %s: connection refused (%d)", s, err); + else post("udpsend~: %s: %s (%d)", s, strerror(err), err); +#else + int err = errno; + post("udpsend~: %s: %s (%d)", s, strerror(err), err); +#endif +#ifdef _WIN32 + if (err == WSAEWOULDBLOCK) +#endif +#if defined(UNIX) || defined(unix) + if (err == EAGAIN) +#endif + { + return 1; /* recoverable error */ + } + return 0; /* indicate non-recoverable error */ +} + +static void udpsend_tilde_closesocket(int fd) +{ +#if defined(UNIX) || defined(unix) + close(fd); +#endif +#ifdef _WIN32 + closesocket(fd); +#endif +} + +/* fin udpsend~.c */ diff --git a/udpsend~.h b/udpsend~.h new file mode 100644 index 0000000..386b363 --- /dev/null +++ b/udpsend~.h @@ -0,0 +1,102 @@ +/* udpsend~.h modified by Martin Peach from netsend~.h: */ +/* ------------------------ netsend~ ------------------------------------------ */ +/* */ +/* Tilde object to send uncompressed audio data to netreceive~. */ +/* Written by Olaf Matthes . */ +/* Based on streamout~ by Guenter Geiger. */ +/* Get source at http://www.akustische-kunst.org/ */ +/* */ +/* 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. */ +/* */ +/* See file LICENSE for further informations on licensing terms. */ +/* */ +/* 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, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* This project was commissioned by the Society for Arts and Technology [SAT], */ +/* Montreal, Quebec, Canada, http://www.sat.qc.ca/. */ +/* */ +/* ---------------------------------------------------------------------------- */ + + +/* This file is based on and inspired by stream.h (C) Guenter Geiger 1999. */ +/* Some enhancements have been made with the goal of keeping compatibility */ +/* between the stream formats of streamout~/in~ and netsend~/receive~. */ + +#define VERSION "0.34" + +#define DEFAULT_AUDIO_CHANNELS 32 /* nax. number of audio channels we support */ +#define DEFAULT_AUDIO_BUFFER_SIZE 2048 /*1024*/ /* number of samples in one audio block */ +#define DEFAULT_UDP_PACKT_SIZE 8192 /* number of bytes we send in one UDP datagram (OS X only) */ +#define DEFAULT_PORT 8000 /* default network port number */ + +#ifdef _WIN32 +#ifndef HAVE_INT32_T +typedef int int32_t; +#define HAVE_INT32_T +#endif +#ifndef HAVE_INT16_T +typedef short int16_t; +#define HAVE_INT16_T +#endif +#ifndef HAVE_U_INT32_T +typedef unsigned int u_int32_t; +#define HAVE_U_INT32_T +#endif +#ifndef HAVE_U_INT16_T +typedef unsigned short u_int16_t; +#define HAVE_U_INT16_T +#endif +#endif + +typedef union _flint +{ + int i32; + t_float f32; +} flint; + +/* format specific stuff */ + +#define SF_FLOAT 1 +#define SF_DOUBLE 2 /* not implemented */ +#define SF_8BIT 10 +#define SF_16BIT 11 +#define SF_32BIT 12 /* not implemented */ +#define SF_ALAW 20 /* not implemented */ +#define SF_MP3 30 /* not implemented */ +#define SF_AAC 31 /* AAC encoding using FAAC */ +#define SF_VORBIS 40 /* not implemented */ +#define SF_FLAC 50 /* not implemented */ + +#define SF_SIZEOF(a) (a == SF_FLOAT ? sizeof(t_float) : \ + a == SF_16BIT ? sizeof(short) : 1) + +typedef struct _tag +{ /* size (bytes) */ + char tag[4]; /* 4 */ /*"TAG!"*/ + char format; /* 1 */ + long count; /* 4 */ + char channels; /* 1 */ + long framesize; /* 4 */ + char reserved[2]; /* 2 */ /* pad to 16 bytes */ +} t_tag; /*-----*/ + /* 16 */ + +typedef struct _frame +{ + t_tag tag; + char *data; +} t_frame; + +/* fin udpsend~.h */ -- cgit v1.2.1