#!/bin/bash # I, Ian Kelling, follow the GNU license recommendations at # https://www.gnu.org/licenses/license-recommendations.en.html. They # recommend that small programs, < 300 lines, be licensed under the # Apache License 2.0. This file contains or is part of one or more small # programs. If a small program grows beyond 300 lines, I plan to change # to a recommended GPL license. # Copyright 2024 Ian Kelling # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ffs = ffmpeg stream # todo: learn to start working in one corner of the screen. # todo: get an icecast on li.b8.nz for when i'm away from home. # potential improvement: it might be nice that we could have a tall terminal bug only use # the top half for a 1080p stream, this is how: # https://superuser.com/questions/1106674/how-to-add-blank-lines-above-the-bottom-in-terminal # # potential improvement: sometimes I probably want to stream full height # window. I could add an option for that. # potential improvement: setup a function which automatically mutes after some time, or # after some time of no audio input. ### begin background/development docs ### # zmq vs stdin commands: # # * zmq allows targeting a specific filter when there are duplicates. # # * if you type stdin command too slow, ffmpeg will die because it stops # doing normal work while it is waiting. # # * zmq returns a result to the calling process, rather than printing to # stdout. # # * the only simple zmq tool I found, zmqsend, requires compiling. I # used the latest ffmpeg 7.0.1. Build like so: # # p build-dep ffmpeg/aramo # ./configure --enable-libzmq # i already had libzmq3-dev installed # make alltools # cp tools/zmqsend /a/opt/bin # # * ffmpeg debug output was useful in testing zmq commands. # # * Important documentation for stdin commands is only found by typing # "c" into the stdin of ffmpeg and reading the output. # # # # stdin command docs, before I abandoned it for zmq: # mkfifo -m 0600 /tmp/iank-ffmpeg # # ffmpeg sits and waits until we do this. dunno why. # echo >/tmp/iank-ffmpeg & # ffmpeg ... /tmp/iank-ffmpeg # echo "cdrawbox -1 t fill" >/tmp/iank-ffmpeg # echo "cdrawtext -1 reinit text=''" >/tmp/iank-ffmpeg # echo "cvolume -1 volume=1" >/tmp/iank-ffmpeg # For testing: to show the number of audio channels in a resulting file # https://stackoverflow.com/questions/47905083/how-to-check-number-of-channels-in-my-audio-wav-file-using-ffmpeg-command # # ffprobe -i /tmp/out.wav -show_entries stream=channels -select_streams a:0 -of compact=p=0:nk=1 -v 0 # for a right/left speaker test: # https://askubuntu.com/questions/148363/which-linux-command-can-i-use-to-test-my-speakers-for-current-talk-radio-output # p install alsa-utils # speaker-test -t wav -c 2 -l 1 # There are 2 other options for audio, so I wanted to do a little # performance measurement of this method. # 1 is to combine the 2 audio sources in pulse, # https://unix.stackexchange.com/questions/351764/create-combined-source-in-pulseaudio . # 1 is to record mumble and combine in post processing. ### benchmark / perf tests: these are pretty inaccurate. # 29 seconds cpu use. video bitrate 1500k, 8 fps, 2x keyframe interval. # * 64k vorbis: 69.7% # * 128k vorbis: 70.1% (used in subsequent tests) # * 1 audio input: 64.3% # * 0 audio inputs: 59.2% # how I did perf testing: add -to 00:00:30 to ffmpeg opts to # conveniently exit after measurement. Then run: # # ffmpeg "${opts[@]}" & # pid=$! # sleep 29 # ps -p $pid -o %cpu # kill %% # filter for only 1 audio input: #-filter_complex "[0]azmq,volume=precision=fixed;[1]zmq='b=tcp\://127.0.0.1\:5557',drawbox=color=0x262626,drawtext=fontsize=90: fontcolor=beige: x=40: y=40: text=''" # filter with 0 audio input: # -filter_complex "[0]zmq='b=tcp\://127.0.0.1\:5557',drawbox=color=0x262626,drawtext=fontsize=90: fontcolor=beige: x=40: y=40: text=''" # When things weren't working, I did some checking on newer ffmpeg to # see if that helped. It never did. I compiled the latest ffmpeg release # tarball, 7.0.1, and tried the version in debian bullseye by schrooting # before running ffmpeg. Building was just configure; make, but then I # found some flags that were needed. gpl flags r just because I noticed them. # ./configure --enable-libzmq --enable-libpulse --enable-libvorbis --enable-gpl --enable-version3 # ### end background/development docs ### if ! test "$BASH_VERSION"; then echo "error: shell is not bash" >&2; exit 1; fi shopt -s inherit_errexit 2>/dev/null ||: # ignore fail in bash < 4.4 set -eE -o pipefail trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" exit status: $?, PIPESTATUS: ${PIPESTATUS[*]}" >&2' ERR debug=false loglevel=fatal # 3 mountpoints: fsf-sysops (default, public), fsf (all staff), fsf-tech (tech team) # # note: duplicated in ffp mount_suffix=-sysops while [[ $1 ]]; do case $1 in sysops|tech) mount_suffix=-$1 ;; staff) mount_suffix= ;; -d) debug=true loglevel=debug ;; esac shift done host=live.iankelling.org:8000 if [[ $(dig +short @10.2.0.1 -x 10.2.0.2 2>&1 ||:) == kd.b8.nz. ]] \ && ip n show 10.2.0.1 | grep . &>/dev/null; then host=127.0.0.1:8000 fi pass=$(sed -n 's/ *\([^<]*\).*/\1/p' /p/c/icecast.xml) tmpf=$(mktemp) xrandr >$tmpf # example xrandr output: 1280x800+0+0 primary_res=$(awk '$2 == "connected" && $3 == "primary" { print $4 }' $tmpf | sed 's/+.*//') secondary_res=$(awk '$2 == "connected" && $3 != "primary" { print $3 }' $tmpf | sed 's/+.*//') if [[ $secondary_res ]]; then # assumes secondary is on the right x_offset=${primary_res%%x*} secondary_x=${secondary_res%%x*} secondary_y=${secondary_res##*x} stream_res=$(( secondary_x / 2 ))x$(( secondary_y / 2)) else x_offset=0 stream_res=$primary_res fi framerate=8 keyframe_interval=$((framerate * 2)) # Monitor of default sink. # eg: alsa_output.usb-Audio-gd_Audio-gd-00.analog-stereo pa_sink=$(pacmd list-sinks | awk '/\*/ {getline; print $2}' | sed 's/^$//').monitor opts=( # global options # be relatively quiet. switch to debug when testing. -v $loglevel -hide_banner -nostats # note: ordering of inputs also affects zmqsend commands. ## audio input options -f pulse -name ffs # note: duplicated above -thread_queue_size 160 -fragment_size 512 -i default -f pulse # this is for ffmpeg warnings. doesnt seem to affect latency. -thread_queue_size 160 # pulse knows this name somewhere -name ffsdesktop # This fixes latency. i haven't tried tuning it, but going too low creates # choppy output. -fragment_size 512 -i "$pa_sink" ## video input options -video_size $stream_res -f x11grab -framerate $framerate -i :0.0+$x_offset.0 # Video + audio filter. Note: this has only the things we actually need in it. # # volume=precision=fixed fixes this error: # The following filters could not choose their formats: Parsed_amerge_4. # # Default volume precision is float. Our input is fixed. maybe ffmpeg # thinks the input could change and so can't commit to an output. # The error suggests using aformat, which seems like it would probably # also fix the error. # # man page say zmq url default includes "localhost", but specifying a # localhost url caused an error for me. -filter_complex "[0]azmq,volume=precision=fixed: volume=0 [vol0]; [1]azmq='b=tcp\://127.0.0.1\:5556',volume=precision=fixed: volume=0 [vol1]; [vol0][vol1] amerge=inputs=2 [out]; [2]zmq='b=tcp\://127.0.0.1\:5557',drawbox=color=0x262626,drawtext=fontsize=90: fontcolor=beige: x=40: y=40: text=''" # Based on error message and poking around, it seems ffmpeg is not # smart enough to see that [vol0] and [vol1] are inputs to the amerge # filter, and thus we would not want them as final outputs. So, we # have to identify the amerge output and pass it to -out. This # identifier is called an "output pad" in man ffmpeg-filters, and a # "link label" in man ffmpeg. -map '[out]' # video output options -vcodec libvpx -g $keyframe_interval -quality realtime # for 1080p, default 256k is poor quality. 500 is ok. 1500 is a bit better. -b:v 1500k -threads 2 -buffer_duration 10 -error-resilient 1 ## audio output options -c:a libvorbis -b:a 128k # afaik, this ensures that the amerge doesn't make 4 channel output if # our output format supported it. -ac 2 -content_type video/webm -f webm icecast://source:$pass@$host/fsf$mount_suffix.webm ) rm -f /tmp/iank-ffmpeg-interlude-toggle # start muted pactl set-source-mute @DEFAULT_SOURCE@ true #echo executing: ffmpeg ${opts[@]} #{ sleep 1; ffp &>/dev/null & } if $debug; then ffmpeg "${opts[@]}" exit 0 fi # For now, we want to watch the stream and end the stream when we stop watching. ffmpeg "${opts[@]}" & sleep 2 ffp ||: kill %%