ref: cff0ebade5fb37b3d5614ae9ff76513e4b0e4640
parent: 48f53e57be61f7cee021fdb21849d4759770f722
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Mon Dec 5 10:46:27 EST 2022
audio/scream: multicast audio protocol
--- /dev/null
+++ b/sys/man/1/scream
@@ -1,0 +1,77 @@
+.TH SCREAM 1
+.SH NAME
+screamsend, screamrecv, screamenc, screamdec \- multicast audio protocol
+.SH SYNOPSIS
+.B audio/screamsend
+[
+.I interfaceip
+]...
+.br
+.B audio/screamrecv
+[
+.I interfaceip
+]...
+.br
+.B audio/screamenc
+.br
+.B audio/screamdec
+.SH DESCRIPTION
+.PP
+Scream is a simple network protocol for transmitting PCM audio on a local network.
+It sends UDP packets at a constant rate to the multicast address
+.B 239.255.77.77
+on port
+.BR 4010 .
+Each packet starts with a small 5-byte header that contains information about the
+sample-rate and data format followed raw PCM data payload (maximum 1157 bytes).
+.PP
+.I Screamsend
+reads PCM audio from
+.B /dev/audio
+and sends scream packets to the local network on the interface given by
+.IR interfaceip.
+When
+.I interfaceip
+is omitted, it uses first IPv4 interface ip address from
+.B /net/ipselftab
+as a default.
+.PP
+.I Screamrecv
+listens for packets from the local network on the interfaces
+selected by
+.IR interfaceip
+and writes PCM audio to
+.BR /dev/audio .
+When no
+.I interfaceip
+addresses where given, it will listen on all interfaces with an IPv4 address.
+.PP
+Both
+.I screamsend
+and
+.I screamrecv
+are usually run after
+.I audio/mixfs
+(see
+.IR audio (1))
+to provide loopback audio source as well as mixing for multiple senders.
+.PP
+.I Screamenc
+reads PCM audio from standard-input and writes scream packets to standard-output.
+.PP
+.I Screamdec
+reads scream packets from standard-input, and writes PCM audio to standard-output.
+It spawns
+.I audio/pcmconv
+(see
+.IR audio (1))
+to convert the audio in case the scream packet format is not
+the default of 16-bit little-endian stereo samples at 44100 Hz.
+It exits when no packets have arrived for 500 milliseconds.
+.SH SOURCE
+.B /sys/src/cmd/audio/scream
+.SH SEE ALSO
+.IR audio (1).
+.br
+.B https://github.com/duncanthrax/scream
+
--- a/sys/src/cmd/audio/mkfile
+++ b/sys/src/cmd/audio/mkfile
@@ -1,7 +1,7 @@
</$objtype/mkfile
LIBS=libogg libvorbis libFLAC libtags
-PROGS=pcmconv oggdec oggenc mp3dec mp3enc flacdec flacenc wavdec sundec mixfs readtags zuke
+PROGS=pcmconv oggdec oggenc mp3dec mp3enc flacdec flacenc wavdec sundec mixfs readtags zuke scream
#libs must be made first
DIRS=$LIBS $PROGS
--- /dev/null
+++ b/sys/src/cmd/audio/scream/mkfile
@@ -1,0 +1,21 @@
+</$objtype/mkfile
+<../config
+
+TARG=screamenc screamdec
+RC=screamsend screamrecv
+
+</sys/src/cmd/mkmany
+
+$O.screamenc: screamenc.$O
+$O.screamdec: screamdec.$O
+
+# Override install target to install rc.
+install:V:
+ for (i in $TARG)
+ mk $MKFLAGS $i.install
+ for (i in $RC)
+ mk $MKFLAGS $i.rcinstall
+
+%.rcinstall:V:
+ cp $stem $BIN/$stem
+ chmod +x $BIN/$stem
--- /dev/null
+++ b/sys/src/cmd/audio/scream/screamdec.c
@@ -1,0 +1,71 @@
+#include <u.h>
+#include <libc.h>
+
+char deffmt[] = "s16c2r44100";
+char fmt[64];
+uchar hdr[5];
+uchar buf[2048];
+int pfd[2];
+
+char*
+getformat(uchar hdr[5])
+{
+ int freq, bits, chan;
+
+ if(hdr[0] & 0x80)
+ freq = 44100;
+ else
+ freq = 48000;
+ freq *= hdr[0] & 0x7F;
+ bits = hdr[1];
+ chan = hdr[2];
+ snprint(fmt, sizeof(fmt), "s%dc%dr%d", bits, chan, freq);
+ return fmt;
+}
+
+void
+main(void)
+{
+ int n;
+
+ for(;;){
+ alarm(500);
+
+ n = read(0, buf, sizeof(buf));
+ if(n < sizeof(hdr))
+ break;
+
+ if(pfd[1] == 0 || memcmp(buf, hdr, sizeof(hdr)) != 0){
+ if(pfd[1] > 1){
+ close(pfd[1]);
+ waitpid();
+ }
+ if(strcmp(getformat(buf), deffmt) == 0){
+ pfd[1] = 1;
+ } else {
+ if(pipe(pfd) < 0)
+ sysfatal("pipe: %r");
+ switch(fork()){
+ case -1:
+ sysfatal("fork: %r");
+ case 0:
+ close(pfd[1]);
+ dup(pfd[0], 0);
+ execl("/bin/audio/pcmconv", "pcmconv", "-i", fmt, nil);
+ sysfatal("exec: %r");
+ return;
+ }
+ close(pfd[0]);
+ }
+ memmove(hdr, buf, sizeof(hdr));
+ }
+
+ n -= sizeof(hdr);
+ if(n <= 0)
+ continue;
+
+ if(write(pfd[1], buf+sizeof(hdr), n) != n)
+ break;
+ }
+ exits(nil);
+}
--- /dev/null
+++ b/sys/src/cmd/audio/scream/screamenc.c
@@ -1,0 +1,32 @@
+#include <u.h>
+#include <libc.h>
+
+int freq = 44100;
+int chan = 2;
+int bps = 16;
+int delay = 5;
+
+uchar buf[2048];
+
+void
+main(void)
+{
+ int n, m;
+
+ if((freq % 44100) == 0){
+ buf[0] = 0x80 | (freq / 44100);
+ } else {
+ buf[0] = freq / 48000;
+ }
+ buf[1] = bps;
+ buf[2] = chan;
+ buf[3] = 0;
+ buf[4] = 0;
+
+ n = (bps/8)*chan*((delay*freq+999)/1000);
+ while((m = read(0, buf+5, n)) > 0){
+ if(write(1, buf, 5+m) < 0)
+ sysfatal("write: %r");
+ }
+ exits(nil);
+}
--- /dev/null
+++ b/sys/src/cmd/audio/scream/screamrecv
@@ -1,0 +1,13 @@
+#!/bin/rc
+rfork e
+lifc=()
+while(~ $1 *.*.*.*){
+ lifc=($lifc $1)
+ shift
+}
+if(! ~ $#* 0){
+ echo 'Usage: audio/screamrecv [interfaceip]...' >[1=2]
+ exit 'usage'
+}
+if(~ $#lifc 0) lifc=`{awk '/4u/{print $1}' /net/ipselftab}
+exec aux/listen1 -t -p4 -O^'addmulti '^$lifc udp!239.255.77.77!4010 rc -c 'exec audio/screamdec > /dev/audio'
--- /dev/null
+++ b/sys/src/cmd/audio/scream/screamsend
@@ -1,0 +1,13 @@
+#!/bin/rc
+rfork e
+lifc=()
+while(~ $1 *.*.*.*){
+ lifc=($lifc $1)
+ shift
+}
+if(! ~ $#* 0){
+ echo 'Usage: audio/screamsend [interfaceip]...' >[1=2]
+ exit 'usage'
+}
+if(~ $#lifc 0) lifc=`{awk '/4u/{print $1}' /net/ipselftab}
+exec audio/screamenc < /dev/audio | exec aux/trampoline -o^'addmulti '^$lifc udp!239.255.77.77!4010