home *** CD-ROM | disk | FTP | other *** search
- /***********************************************************
- Copyright 1991, 1992 by Stichting Mathematisch Centrum, Amsterdam, The
- Netherlands.
-
- All Rights Reserved
-
- Permission to use, copy, modify, and distribute this software and its
- documentation for any purpose and without fee is hereby granted,
- provided that the above copyright notice appear in all copies and that
- both that copyright notice and this permission notice appear in
- supporting documentation, and that the names of Stichting Mathematisch
- Centrum or CWI not be used in advertising or publicity pertaining to
- distribution of the software without specific, written prior permission.
-
- STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
- THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
- FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
- FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
- OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
- ******************************************************************/
-
- /* Receive audio UDP packets transmitted by broadcast.
-
- Command line options:
-
- -p port tune to this port number (default 54321)
- (port numbers 1..99 are shorthands for 54321 and up)
- -v volume output volume (range 0-100; default unchanged)
- (you can auso use [x_]gaintool or a similar tool to
- set the output volume etc.)
- -c port use this control port (default 54320)
- -s 'secure' mode: don't listen to the control port
- -f work as a filter: send output to stdout instead of
- directly to the audio hardware
- -l addr listen only for packets to <arg> ip address
- -r addr listen only for packets from <arg>
- -d debug packets
- -n noninterruptable -- by default radio will be interruptable
- by other sound outputting programs, hoping they do not
- take too long. This option turns off that feature.
- -t tee mode: send output to stdout as well as to audio h/w
- -m mcastgrp multicast group (SGI only)
- */
-
- #include "radio.h"
- #include "patchlevel.h"
-
- #include <stdio.h>
- #include <errno.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #include <signal.h>
- #include <netdb.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/time.h>
- #include <netinet/in.h>
-
- #include "adpcm.h"
-
- #ifdef HAVE_MCAST
- #include <arpa/inet.h>
-
- #ifdef DEFMCAST
- char defmcast[] = DEFMCAST;
- #else
- char *defmcast = 0;
- #endif /* DEFMCAST */
-
- #endif /* HAVE_MCAST */
-
- #ifdef USE_AL
- #include <audio.h>
- #include "libst.h"
-
- long savestate[] = {
- AL_OUTPUT_RATE, 0,
- /* The following two must be the last pairs! */
- AL_LEFT_SPEAKER_GAIN, 0,
- AL_RIGHT_SPEAKER_GAIN, 0,
- };
-
- ALport aport;
-
- void cleanup_handler();
- #endif /* USE_AL */
-
- #ifdef USE_SUN
- #include <stropts.h>
- #include <sun/audioio.h>
-
- #define AUDIO_IODEV "/dev/audio"
- #define AUDIO_CTLDEV "/dev/audioctl"
-
- int interruptable = 1;
- int actlfd = -1;
- int afd = -1;
-
- void sigpoll_handler();
- #endif /* USE_SUN */
-
- #ifdef USE_NX
- #include <sound/sound.h>
- #define NUM_BUFFER 10
- SNDSoundStruct *snd[NUM_BUFFER];
- #endif /* USE_NX */
-
- #ifdef CHECK_X_SERVER
- #include <X11/Xlib.h>
- Display *xdisplay = 0;
- #endif /* CHECK_X_SERVER */
-
- /* getopt() interface */
- extern int optind;
- extern char * optarg;
-
- /* Globals */
- int pausing = 0; /* Flag set when pausing */
- int ofd = -1; /* Output file descriptor */
- int volume = -1; /* -v parameter */
- int pdebug = 0; /* -p parameter */
- char *mcastgrp = 0; /* -m parameter */
-
- /* Forward functions */
- void open_speaker();
- void close_speaker();
- void checkalive();
- void setmcast(); /* Forward */
-
- main(argc, argv)
- int argc;
- char **argv;
- {
- int receiveport = RCVPORT;
- int ctlport = RADIOCTLPORT;
- char real_buf[BUFFERSIZE + HEADERSIZE];
- char tmp_buf[BUFFERSIZE];
- int encoding;
- char *buf;
- int s, ctls, curs;
- struct sockaddr from;
- int fromlen;
- int c;
- int filter = 0;
- int tee = 0;
- int nfds;
- fd_set inputset;
- int n;
- char *localname = (char *) NULL;
- char *remotename = (char *) NULL;
- struct timeval timeout;
- int packetcount;
- struct adpcm_state state;
- #ifdef USE_AL
- short obuf[BUFFERSIZE];
- int i;
- int curdatalinear;
- #endif
- #ifdef USE_NX
- int akt_buf;
- #endif
-
- /* Always change these two macros and the following switch together! */
- #define OPTIONS "c:dfl:m:np:r:stv:"
- #define USAGE "usage: %s [options]\n\
- User options:\n\
- -p port : port to listen to (default 54321; 1..99 ==> 54321..54419)\n\
- -v volume : volume setting (1-100; default unchanged)\n\
- Expert options:\n\
- -f : filter mode (write data to stdout)\n\
- -t : tee mode (write data to stdout as well as to audio device)\n\
- -n : not interruptable by other sources (Sun only)\n\
- -c ctlport : control port for tuner programs (default 54320)\n\
- -s : secure mode: no control port (disallow tuner programs)\n\
- Guru options:\n\
- -l localhost : listen to packets to this host only\n\
- -r remothost : receive packets from that host only\n\
- -m mcastgrp : multicast group (not always supported)\n\
- -d : debugging mode (writes messages to stderr)\n\
- "
-
- while ((c = getopt(argc, argv, OPTIONS)) != EOF) {
- switch (c) {
- case '?':
- fprintf(stderr, USAGE, argv[0]);
- exit(2);
- case 'p':
- receiveport = atoi(optarg);
- if (0 < receiveport && receiveport < 100)
- receiveport += RCVPORT-1;
- break;
- case 'c':
- ctlport = atoi(optarg);
- break;
- case 'l':
- localname = optarg;
- break;
- case 'r':
- remotename = optarg;
- break;
- case 'd':
- pdebug = 1;
- break;
- case 'm':
- #ifdef HAVE_MCAST
- mcastgrp = optarg;
- #else
- fprintf(stderr, "(-m not supported here)\n");
- #endif
- break;
- case 'n':
- #ifdef USE_SUN
- interruptable = 0;
- #else
- fprintf(stderr, "(-n not supported here)\n");
- #endif
- break;
- case 's':
- ctlport = -1;
- break;
- case 'f':
- filter = 1;
- tee = 0;
- break;
- case 't':
- tee = 1;
- filter = 0;
- break;
- case 'v':
- volume = atoi(optarg);
- break;
- }
- }
-
- /* Meaning of the 'tee' and 'filter' flags:
- At most one of these can be on.
- if tee is on: write stdout and "/dev/audio";
- if filter is on: write stdout only;
- if both are off: write "/dev/audio" only;
- where "/dev/audio" stands for whatever audio hardware we have. */
-
- if (filter || tee)
- ofd = fileno(stdout);
- if (!filter) {
- open_speaker();
- #ifdef CHECK_X_SERVER
- xdisplay = XOpenDisplay((char *)NULL);
- if (xdisplay == NULL) {
- fprintf(stderr,
- "radio: warning: no X server -- you must kill radio when you log out!\n");
- }
- #endif
- }
-
- if (ctlport >= 0)
- ctls = opensock("control", (char *)NULL, ctlport,
- (char *)NULL, 0, 0);
- else
- ctls = -1;
-
- s = opensock("data", localname, receiveport, remotename, SENDPORT, 0);
- #ifdef HAVE_MCAST
- if (mcastgrp)
- setmcast(s, mcastgrp);
- else if (defmcast)
- setmcast(s, defmcast);
- #endif
-
- packetcount = 0;
-
- for (;;) {
- /*
- ** Wait until one of the sockets becomes ready
- */
- for (;;) {
- nfds = (s > ctls ? s : ctls) + 1;
- FD_ZERO(&inputset);
- FD_SET(s, &inputset);
- if (ctls >= 0)
- FD_SET(ctls, &inputset);
- timeout.tv_sec = 30;
- timeout.tv_usec = 0;
- n = select(nfds, &inputset, 0, 0, &timeout);
- if (n > 0)
- break;
- if (n == 0) {
- checkalive();
- }
- else if (errno != EINTR) {
- perror("select");
- exit(1);
- }
- }
- if (ctls >= 0 && FD_ISSET(ctls, &inputset))
- curs = ctls;
- else if (FD_ISSET(s, &inputset))
- curs = s;
- /*
- ** Read, and check for control packet
- */
- fromlen = sizeof(from);
- buf = real_buf;
- n = recvfrom(curs, buf, HEADERSIZE + BUFFERSIZE, 0,
- &from, &fromlen);
- if (n <= 0) {
- if (n == 0)
- continue; /* Ignore empty packets */
- perror("read");
- break;
- }
- if (pdebug) {
- if(pdebug == 8) {
- fprintf(stderr, "8 packets received\n");
- pdebug = 1;
- }
- else
- pdebug++;
- }
- if (n <= CTLPKTSIZE) {
- /*
- ** It looks like a control packet. Check it.
- */
- buf[n] = '\0';
- if (strncmp(buf, "radio:", 6) == 0) {
- if (pdebug)
- fprintf(stderr, "control packet\n");
- switch(buf[6]) {
- case 'e': /* Echo */
- buf[6] = 'E';
- sendto(curs, buf, n, 0,
- &from, fromlen);
- break;
- case 'S': /* Status from broadcast */
- if (pdebug)
- fprintf(stderr,
- "Status %s\n", buf);
- break;
- case 't': /* Tune */
- if (curs != ctls) {
- if (pdebug)
- fprintf(stderr,
- "radio: illegal tune\n");
- break;
- }
- #ifdef USE_SUN
- if (!filter) {
- (void) ioctl(ofd, I_FLUSH,
- FLUSHW);
- }
- #endif /* USE_SUN */
- receiveport = atoi(buf+8);
- close(s);
- s = opensock("new data", localname,
- receiveport, remotename,
- SENDPORT, 0);
- if (mcastgrp)
- setmcast(s, mcastgrp);
- break;
- case 'i': /* Info */
- sprintf(buf, "radio:I:%d:%d:%s.%d",
- !pausing, receiveport,
- VERSION, PATCHLEVEL);
- sendto(curs, buf, strlen(buf), 0,
- &from, fromlen);
- break;
- #ifndef USE_NX /* XXX I don't know how to close_speaker() on the NeXT */
- case 'p': /* Pause */
- case '0': /* Backward compatibility */
- if (!filter && !pausing) {
- close_speaker();
- pausing = 1;
- }
- break;
- case 'c': /* Continue */
- case '1': /* Backward compatibility */
- if (pausing) {
- open_speaker();
- pausing = 0;
- }
- break;
- #endif /* USE_NX */
- default:
- if (pdebug)
- fprintf(stderr,
- "radio: illegal cmd '%c'\n",
- buf[6]);
- }
- }
- else if (pdebug) {
- fprintf(stderr,
- "radio: ill-formatted command\n");
- }
- }
- else if (!pausing) {
- encoding = PCM_64;
- #ifdef USE_AL
- curdatalinear = 0;
- #endif /* USE_AL */
- if ((buf[0]&0xff) == AUDIO_TYPE) {
- encoding = buf[1]&0xff;
- buf += HEADERSIZE;
- n -= HEADERSIZE;
- }
- else {
- if (pdebug)
- fprintf(stderr,
- "radio: non-IVS packet\n");
- continue;
- }
- switch (encoding) {
-
- case PCM_64:
- break;
-
- case ADPCM_32:
- n = n*2;
- #ifdef USE_AL
- /*
- ** For SGI and non-filter mode, don't convert
- ** via ulaw but straight to linear
- */
- if (!filter) {
- curdatalinear = 1;
- adpcm_decoder(buf, obuf, n,
- (struct adpcm_state *)0);
- }
- else
- #endif /* USE_AL */
- {
- adpcm_ulaw_decoder(buf, tmp_buf, n,
- (struct adpcm_state *)0);
- buf = tmp_buf;
- }
- break;
-
- case ADPCM_32_W_STATE:
- state.valprev =
- ((buf[0]&0xff)<<8) | (buf[1]&0xff);
- state.index = buf[2]&0xff;
- buf += 3;
- n -= 3;
- n = n*2;
- #ifdef USE_AL
- if (!filter) {
- adpcm_decoder(buf, obuf, n, &state);
- curdatalinear = 1;
- }
- else
- #endif /* USE_AL */
- {
- adpcm_ulaw_decoder(buf, tmp_buf, n,
- &state);
- buf = tmp_buf;
- }
- break;
-
- default:
- if (pdebug)
- fprintf(stderr,
- "radio: unknown encoding %d\n",
- encoding);
- /* Ignore the package */
- continue;
-
- }
- #ifdef USE_AL
- if (!filter) {
- if (!curdatalinear)
- for (i = 0; i < n; i++)
- obuf[i] =
- st_ulaw_to_linear(buf[i]&0xff);
- ALwritesamps(aport, obuf, (long)n);
- }
- #endif /* USE_AL */
- #ifdef USE_NX
- if (!filter) {
- int dummy;
- char *ptr;
-
- (void) SNDGetDataPointer(snd[akt_buf], &ptr,
- &dummy, &dummy);
-
- SNDWait(akt_buf+1);
- memcpy(ptr, buf, n);
- snd[akt_buf] -> dataSize = n;
- SNDStartPlaying(snd[akt_buf],
- akt_buf+1, 5, 0, 0, 0);
- akt_buf = (akt_buf + 1) % NUM_BUFFER;
- }
- #endif /* USE_NX */
- #ifdef USE_SUN
- if (!filter) {
- if (write(afd, buf, n) != n) {
- perror("write afd");
- break;
- }
- }
- #endif /* USE_SUN */
- if (filter || tee) {
- if (write(ofd, buf, n) != n) {
- perror("write ofd");
- break;
- }
- }
- }
- if (++packetcount > (30*SAMPLINGRATE / BUFFERSIZE)) {
- checkalive();
- packetcount = 0;
- }
- }
-
- exit(0);
- }
-
- #ifdef USE_AL
-
- void open_speaker()
- {
- ALconfig config;
- long pvbuf[6];
-
- /* Fetch the original state */
- ALgetparams(AL_DEFAULT_DEVICE, savestate,
- sizeof(savestate) / sizeof(long));
-
- /* Set signal handlers */
- signal(SIGINT, cleanup_handler);
- signal(SIGTERM, cleanup_handler);
-
- /* Set the output sampling rate */
- pvbuf[0] = AL_OUTPUT_RATE;
- pvbuf[1] = SAMPLINGRATE; /* XXX Assume AL_RATE_n is n */
-
- /* Maybe also set the volume */
- if (volume >= 0) {
- pvbuf[2] = AL_LEFT_SPEAKER_GAIN;
- pvbuf[3] = volume*255/100;
- pvbuf[4] = AL_RIGHT_SPEAKER_GAIN;
- pvbuf[5] = volume*255/100;
- ALsetparams(AL_DEFAULT_DEVICE, pvbuf, 6L);
- }
- else
- ALsetparams(AL_DEFAULT_DEVICE, pvbuf, 2L);
-
- /* Configure and open an SGI audio port */
- config = ALnewconfig();
- ALsetchannels(config, AL_MONO);
- ALsetwidth(config, AL_SAMPLE_16);
- ALsetqueuesize(config, 16000); /* 2 seconds slop */
- aport = ALopenport("radio", "w", config);
- if (aport == NULL) {
- perror("ALopenport");
- exit(1);
- }
- }
-
- void close_speaker()
- {
- ALcloseport(aport);
- aport = NULL;
- }
-
- void cleanup_handler(sig)
- int sig;
- {
- signal(sig, SIG_DFL);
- if (!pausing) {/* Don't reset anything if we're pausing */
- long n = sizeof(savestate) / sizeof(long);
- if (volume < 0)
- n -= 4; /* Don't reset volume if we didn't set it */
- ALsetparams(AL_DEFAULT_DEVICE, savestate, n);
- }
- kill(getpid(), sig);
- }
-
- #endif /* USE_AL */
-
-
- #ifdef USE_SUN
-
- void open_speaker()
- {
- audio_info_t info;
-
- /* Write to AUDIO_IODEV */
- if ((afd = open(AUDIO_IODEV, O_WRONLY)) < 0) {
- perror(AUDIO_IODEV);
- exit(1);
- }
-
- /* Set the volume */
- if (volume >= 0) {
- AUDIO_INITINFO(&info);
- info.play.gain = (AUDIO_MAX_GAIN * volume) / 100;
- if (ioctl(afd, AUDIO_SETINFO, &info))
- perror("volume setting");
- }
-
- /* We need to open the audio control port to detect
- if someone else wants to output to /dev/audio.
- If this fails (e.g., in SunOS 4.0), print a message
- but don't exit. */
- if (interruptable) {
- if ((actlfd = open(AUDIO_CTLDEV, O_RDWR)) < 0) {
- perror(AUDIO_CTLDEV);
- }
- else if (ioctl(actlfd, I_SETSIG, S_MSG) < 0) {
- perror("I_SETSIG");
- }
- else if (signal(SIGPOLL, sigpoll_handler) < 0) {
- perror("signal(SIGPOLL)");
- exit(1);
- }
- }
- }
-
- void close_speaker()
- {
- (void) ioctl(afd, I_FLUSH, FLUSHW);
- close(afd);
- close(actlfd);
- afd = actlfd = -1;
- }
-
- void sigpoll_handler()
- {
- audio_info_t ap;
-
- if (ioctl(actlfd, AUDIO_GETINFO, &ap) < 0) {
- perror("AUDIO_GETINFO");
- }
- else if (ap.play.waiting) {
- (void) ioctl(afd, I_FLUSH, FLUSHW);
- close(afd);
- /* The open() call blocks until we can use the device again */
- if ((afd = open(AUDIO_IODEV, O_WRONLY)) < 0) {
- perror(AUDIO_IODEV);
- exit(1);
- }
- ap.play.waiting = 0;
- if (ioctl(actlfd, AUDIO_SETINFO, &ap) < 0) {
- perror("AUDIO_SETINFO");
- }
- }
- }
-
- #endif /* USE_SUN */
-
-
- #ifdef USE_NX
-
- void open_speaker()
- {
- int akt_buf;
- int err;
-
- /* Alloc NUM_BUFFER Sounds */
- for (akt_buf = NUM_BUFFER; akt_buf > 0; akt_buf--) {
- if (err = SNDAlloc(&snd[akt_buf-1], BUFFERSIZE,
- SND_FORMAT_MULAW_8,
- SND_RATE_CODEC, 1, 4)) {
- fprintf(stderr, "init: %s\n", SNDSoundError(err));
- exit(1);
- }
- }
- akt_buf = 0;
- }
-
- void close_speaker()
- {
- /* XXX how to do this? */
- }
-
- #endif /* USE_NX */
-
-
- void checkalive()
- {
- #ifdef CHECK_X_SERVER
- if (xdisplay) {
- Window focus;
- int revert_to;
- if (pdebug)
- fprintf(stderr, "polling X server...\n");
- /* Do a simple X request that needs a server round trip...
- The error handler will kill us when the server is dead,
- so that radio dies when the user logs out. */
- XGetInputFocus(xdisplay, &focus, &revert_to);
- if (pdebug)
- fprintf(stderr, "X server OK\n");
- }
- else if (pdebug)
- fprintf(stderr, "checkalive() is a no-op\n");
- #endif /* CHECK_X_SERVER */
- }
-
- void setmcast(s, group)
- int s;
- char *group;
- {
- #ifdef HAVE_MCAST
- struct hostent *hostentry;
- struct in_addr grpaddr;
- struct in_addr ifaddr;
- struct ip_mreq mreq;
-
- if (hostentry = gethostbyname(group))
- {
- bcopy(hostentry->h_addr, &grpaddr.s_addr, sizeof(grpaddr.s_addr));
- }
- else
- grpaddr.s_addr = inet_addr(group);
-
- if (!IN_MULTICAST(grpaddr.s_addr)) {
- fprintf(stderr, "Bad multicast group: %s\n", group);
- exit(1);
- }
-
- ifaddr.s_addr = htonl(INADDR_ANY);
-
- mreq.imr_multiaddr = grpaddr;
- mreq.imr_interface = ifaddr;
- if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &mreq, sizeof(mreq)) < 0) {
- perror("setsockopt mreq");
- exit(1);
- }
- #endif /* HAVE_MCAST */
- }
-