commit 5311099dbdc835d9dd7ae4f8f7ebd76ee1f99bb7 Author: Dominika Liberda Date: Tue May 11 21:13:05 2021 +0200 initial commit diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0927556 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,157 @@ +### GNU LESSER GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the +terms and conditions of version 3 of the GNU General Public License, +supplemented by the additional permissions listed below. + +#### 0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the +GNU General Public License. + +"The Library" refers to a covered work governed by this License, other +than an Application or a Combined Work as defined below. + +An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + +A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + +The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +#### 1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +#### 2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + +- a) under this License, provided that you make a good faith effort + to ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or +- b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + +#### 3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a +header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + +- a) Give prominent notice with each copy of the object code that + the Library is used in it and that the Library and its use are + covered by this License. +- b) Accompany the object code with a copy of the GNU GPL and this + license document. + +#### 4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken +together, effectively do not restrict modification of the portions of +the Library contained in the Combined Work and reverse engineering for +debugging such modifications, if you also do each of the following: + +- a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. +- b) Accompany the Combined Work with a copy of the GNU GPL and this + license document. +- c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. +- d) Do one of the following: + - 0) Convey the Minimal Corresponding Source under the terms of + this License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + - 1) Use a suitable shared library mechanism for linking with + the Library. A suitable mechanism is one that (a) uses at run + time a copy of the Library already present on the user's + computer system, and (b) will operate properly with a modified + version of the Library that is interface-compatible with the + Linked Version. +- e) Provide Installation Information, but only if you would + otherwise be required to provide such information under section 6 + of the GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the Application + with a modified version of the Linked Version. (If you use option + 4d0, the Installation Information must accompany the Minimal + Corresponding Source and Corresponding Application Code. If you + use option 4d1, you must provide the Installation Information in + the manner specified by section 6 of the GNU GPL for conveying + Corresponding Source.) + +#### 5. Combined Libraries. + +You may place library facilities that are a work based on the Library +side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + +- a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities, conveyed under the terms of this License. +- b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + +#### 6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +as you received it specifies that a certain numbered version of the +GNU Lesser General Public License "or any later version" applies to +it, you have the option of following the terms and conditions either +of that published version or of any later version published by the +Free Software Foundation. If the Library as you received it does not +specify a version number of the GNU Lesser General Public License, you +may choose any version of the GNU Lesser General Public License ever +published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md new file mode 100644 index 0000000..68f4b5f --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# stream.sh + +A quick-and-dirty solution for streaming audio over TCP/IP between +Linux computers with ALSA. + +# usage + +The architecture consists of a server (Computer With Speakers) and +a client (Computer With Crappier Speakers But We Have Media On It). +The script is self-contained, so you can distribute the same version +to client and server computers. + +On the server, run + +``` +./stream.sh server 1337 +``` + +... and then, on the client, run + +``` +./stream.sh set +./stream.sh client 10.21.37.1 1337 +``` + +This is enough to get you started, you should start hearing audio +from your local applications on the remote computer. By default, +stream.sh uses WAVE in a RIFF container - this provides a high +quality, low-latency solution. In case your network isn't fast +enough to support raw audio data, you can specify a codec and +bitrate: + +``` +./stream.sh client 10.21.37.1 1337 libopus 64 +``` + +# debugging + +stream.sh has a built-in benchmarking functionality that tries to +guess the correct timings for your hardware. If this is inaccurate +and you hear pops, or the audio drops off completely every ~60s, +you may want to run + +``` +./stream.sh benchmark ffmpeg +``` + +and modify the delay variable in the `USER-CONFIGURABLE VARIABLES` +section of stream.sh + + +# building + +``` +./pack.sh > stream.sh +``` diff --git a/bench.sh b/bench.sh new file mode 100755 index 0000000..89f30ef --- /dev/null +++ b/bench.sh @@ -0,0 +1,51 @@ +#!/bin/bash +if [[ "$1" == '' ]]; then + if [[ "$0" != *"bench.sh" ]]; then + echo "usage: $0 bench " + else + echo "usage: $0 " + fi + exit 1 +fi +res=() +uwu=0 + +function count() { + x=0 + for (( i=0; i<${#res[@]}; i++ )); do + x=$((x+${res[i]})) + done + uwu=$((uwu+$((x/${#res[@]})))) + echo "$((x/${#res[@]}))" + res=() +} + +function invoke() { + ( time $@ ) 2>&1 | grep -a real | grep -aPoh "[^(real\t0m\.)].*[^s]" +} + + + +echo "FFmpeg launch benchmark" +echo -n "Burst... " + +for (( i=0; i<100; i++ )); do + res+=($(invoke $1)) +done +count + +echo -n "Set sleep... " +for (( i=0; i<25; i++ )); do + res+=($(invoke $1)) + sleep 0.1 +done +count + +echo -n "Random sleep... " +for (( i=0; i<25; i++ )); do + res+=($(invoke $1)) + sleep 0.$((RANDOM%10)) +done +count + +echo "yeah, try $((uwu/3))" diff --git a/client.sh b/client.sh new file mode 100755 index 0000000..4b8b26e --- /dev/null +++ b/client.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# USER-CONFIGURABLE VARIABLES +len=60 +delay=030 +ffmpeg_bin=ffmpeg +# --- + + +function log() { + echo "[$(date "+%T")] $1" +} +function stream() { + $ffmpeg_bin -re -f alsa -channels 2 -sample_rate 44100 -i hw:Loopback,1,0 -t $1 -f matroska -acodec $fmt -b:a $bitrate - 2>$tmp | nc $ip $port +} +function stream_wave() { + $ffmpeg_bin -re -f alsa -channels 2 -sample_rate 44100 -i hw:Loopback,1,0 -t $1 -f wav - 2>$tmp | nc $ip $port +} +function stream_cont() { + $stream $len & sleep $len.$delay +} +function rolling_rocks() { + while true; do + if [[ "$(cat $tmp | grep -a "Input/output error")" != '' ]]; then + rm $tmp + $stream $((len-1)).9 & + log 'Hit a "Device Busy" error, consider increasing sleep delay!' + fi + sleep 0.1 + done +} + +if [[ "$1" == '' ]]; then + if [[ "$0" != *"client.sh" ]]; then + echo "usage: $0 client [fmt] [kbps]" + else + echo "usage: $0 [fmt] [kbps]" + fi + exit 1 +fi +if [[ "$3" != '' ]]; then + fmt="$3" + stream="stream" + log 'Container: matroska' +else + fmt="wav" + stream="stream_wave" + log 'Container: RIFF' +fi +if [[ "$4" != '' ]]; then + bitrate="$4" +else + bitrate="128k" +fi + +ip=$1 +port=$2 +tmp=$(mktemp) + +rolling_rocks & +log "Loop length: ${len}s" +log "Post-loop delay: ${delay}ms" +log 'Starting main loop...' +while true; do + stream_cont +done diff --git a/pack.sh b/pack.sh new file mode 100755 index 0000000..35492a5 --- /dev/null +++ b/pack.sh @@ -0,0 +1,45 @@ +#!/bin/bash +function parse() { + sed -E 's@#!/bin/bash@@g;s/^/\t/g' +} + +echo -n '#!/bin/bash +function bench() {' +cat bench.sh | parse +echo -n '} + +function client() {' +cat client.sh | parse +echo -n '} + +function server() {' +cat server.sh | parse +echo -n '} + +function server_core() {' +cat server_core.sh | parse +echo -n '} + +function _set() {' +cat set.sh | parse +echo -n '} + +function _unset() {' +cat unset.sh | parse + +echo '} +if [[ "$1" == "server_core" ]]; then + server_core +elif [[ "$1" == "" ]]; then + echo "usage: $0 [params]" +elif [[ "$1" == "client" ]]; then + client $2 $3 $4 $5 $6 +elif [[ "$1" == "bench" ]]; then + bench $2 +elif [[ "$1" == "server" ]]; then + server $2 +elif [[ "$1" == "set" ]]; then + _set +elif [[ "$1" == "unset" ]]; then + _unset +fi' diff --git a/server.sh b/server.sh new file mode 100755 index 0000000..c8b1e11 --- /dev/null +++ b/server.sh @@ -0,0 +1,15 @@ +#!/bin/bash +if [[ "$1" == '' ]]; then + if [[ "$0" != *"server.sh" ]]; then + echo "usage: $0 server " + else + echo "usage: $0 " + fi + exit 1 +fi + +if [[ "$0" == *"server.sh" ]]; then + ncat -k -l -p $1 -c "./server_core.sh" +else + ncat -k -l -p $1 -c "$0 server_core" +fi diff --git a/server_core.sh b/server_core.sh new file mode 100755 index 0000000..3a54843 --- /dev/null +++ b/server_core.sh @@ -0,0 +1,8 @@ +#!/bin/bash +read -n4 container + +if [[ "$container" == "RIFF" ]]; then + (echo -n "$container"; cat -) | aplay -f cd - +else + (echo -n "$container"; cat -) | ffmpeg -re -i - -f wav - | aplay -f cd - +fi diff --git a/set.sh b/set.sh new file mode 100755 index 0000000..13a052d --- /dev/null +++ b/set.sh @@ -0,0 +1,5 @@ +#!/bin/bash +sudo rmmod snd-aloop +sudo modprobe snd-aloop pcm_substreams=1 +[[ -f ~/.asoundrc ]] && cp ~/.asoundrc ~/.asoundrc_orig +echo 'pcm.!default { type plug slave.pcm "hw:Loopback,0,0" }' > ~/.asoundrc diff --git a/unset.sh b/unset.sh new file mode 100755 index 0000000..ecadb22 --- /dev/null +++ b/unset.sh @@ -0,0 +1,4 @@ +#!/bin/bash +sudo rmmod snd-aloop +rm ~/.asoundrc +[[ -f ~/.asoundrc_orig ]] && cp ~/.asoundrc_orig ~/.asoundrc