this repo contains two analogous script prototypes for screencasting to livecoding.tv
- ffmpeg implementation - full-featured and works quite well
- gstreamer implementetion - work in progress
this repo contains two analogous script prototypes for screencasting to livecoding.tv
#!/bin/bash | |
THIS_NAME=`basename $0` | |
USAGE="USAGE: | |
$THIS_NAME [ a_configuration [ once ] ] | |
when called with no arguments the stream runs the default configuration, automatically | |
restarting after any exceptional event (device error , dropped connection , etc) | |
the optional \"a_configuration\" selects a particular configuration and is one of: | |
[ screen , screen+text , screen+cam , cam+text , all , break ] | |
the optional second argument 'once' disables automatic restarts | |
NOTE: STREAM_KEY must be defined either in this script or in the environment | |
NOTE: all configurations include audio - set CAPTURE_AUDIO to 0 to disable" | |
# the entries under the following headings are intended for per-user configuration: | |
# * user a/v params (STREAM_KEY must be defined) | |
# * user image params | |
# * user text params | |
# * user text configuration | |
# * user a/v configuration | |
# * user logging configuration | |
# | |
# the entries under the following headings are general and suitable for most cases: | |
# * general a/v params | |
# * general text params | |
# * general logging params | |
# * general a/v configuration | |
# | |
# when one of the configurations displaying text is chosen | |
# the first line of $TEXT_FILE will be rendered | |
# user a/v params | |
[ ! -z "$STREAM_KEY" ] || STREAM_KEY="" # your private stream key (or define STREAM_KEY in env) | |
SCREEN_W=1280 # input screen width (e.g. desktop width ) | |
SCREEN_H=800 # input screen height (e.g. desktop height) | |
SCREEN_N=":0.0" # display/screen number to capture (usually :0.0) | |
CAM_LG_W=640 # input screen width (e.g. desktop width ) | |
CAM_LG_H=480 # input screen height (e.g. desktop height) | |
CAM_SM_W=160 # input screen width (e.g. desktop width ) | |
CAM_SM_H=120 # input screen height (e.g. desktop height) | |
CAM_DEV=/dev/video42 # video device to grab (first is /dev/video0) | |
STREAM_W=1280 # input screen width (e.g. desktop width ) | |
STREAM_H=800 # input screen height (e.g. desktop height) | |
FPS=12 # target output FPS | |
GOP="$(($FPS*4))" # i-frame interval | |
CBR="1200k" # constant output bitrate (should be between 800k - 1300k) | |
CAPTURE_AUDIO=1 # set to 1 to capture audio - 0 to disable audio | |
AUDIO_CHANNELS=2 # number of channels | |
AUDIO_SAMPLERATE=22050 # audio samplerate (22050 is plenty for voice) | |
AUDIO_BITRATE="128k" # audio bitrate (64k per channel is plenty for voice) | |
ALSA_DEVICE="hw:0" # ALSA plug device (usually hw:0) | |
THREADS=0 # 0 for auto - max 6 | |
QUALITY="ultrafast" # one of the many FFMPEG presets | |
EXIT_CMD="play-error-sound.sh" | |
# user image params | |
[ ! -z "$STREAM_IMG" ] || STREAM_IMG="" # fullscreen interstitial image (or define STREAM_IMG in env) | |
[ ! -z "$LOGO_IMG" ] || LOGO_IMG="" # small overlay image (or define LOGO_IMG in env) | |
# user text params | |
TEXT_FILE=/run/user/`id -u`/av-caster-text | |
[ ! -z "$STREAM_TEXT" ] || STREAM_TEXT="" # initial overlay text (or define STREAM_TEXT in env) | |
FONT_FILE=/usr/share/fonts/truetype/tlwg/Purisa.ttf | |
FONT_SIZE=48 | |
TEXT_BORDER_D=4 | |
TEXT_SHADOW_W=8 | |
TEXT_SHADOW_H=8 | |
BLINK_TEXT_PERIOD=300 # repeat interval in seconds | |
TEXT_MARQUEE_SPEED=6 # ~= n characters per second | |
TEXT_COLOR='[email protected]' | |
TEXT_BORDER_COLOR='[email protected]' | |
TEXT_BG_COLOR='[email protected]' | |
# general a/v params | |
SCREEN_RES=$SCREEN_W'x'$SCREEN_H # screen capture input resolution | |
CAM_HI_RES=$CAM_LG_W'x'$CAM_LG_H # webcam input/output high resolution (fullscreen) | |
CAM_LO_RES=$CAM_SM_W'x'$CAM_SM_H # webcam input/output low resolution (overlay) | |
STREAM_RES=$STREAM_W'x'$STREAM_H # mix output resolution | |
SCREENCAP_INPUT="-f x11grab -s $SCREEN_RES -r $FPS -i $SCREEN_N" | |
WEBCAM_INPUT_LG="-f video4linux2 -s $CAM_HI_RES -i $CAM_DEV" | |
WEBCAM_INPUT_SM="-f video4linux2 -s $CAM_LO_RES -i $CAM_DEV" | |
STATIC_INPUT_LG="-f image2 -loop 1 -r $FPS -i $STREAM_IMG" | |
STATIC_INPUT_SM="-f image2 -loop 1 -r $FPS -i $LOGO_IMG" # unused | |
ALSA_INPUT=" -f alsa -i $ALSA_DEVICE" # unused | |
PULSE_INPUT=" -f pulse -name $THIS_NAME -sample_rate $AUDIO_SAMPLERATE | |
-channels $AUDIO_CHANNELS -i default" # unused | |
JACK_INPUT=" -f jack -channels $AUDIO_CHANNELS -i $THIS_NAME" | |
OVERLAY_TL_POS="0: 0" # unused | |
OVERLAY_BR_POS="main_w-overlay_w: main_h-overlay_h" | |
X264_OUTPUT="-vcodec libx264 -pix_fmt yuv420p -preset $QUALITY -s $STREAM_RES | |
-g $GOP -vb $CBR -maxrate $CBR -bufsize $CBR" | |
AAC_OUTPUT="-acodec aac -ar $AUDIO_SAMPLERATE -ab $AUDIO_BITRATE -strict experimental" | |
THREADS="-threads $THREADS" | |
RTMP_URL="rtmp://usmedia3.livecoding.tv:1935/livecodingtv/$STREAM_KEY" | |
DUMP_FILE=./livecoding.mp4 | |
FILE_OUTPUT="-f flv $DUMP_FILE" | |
LIVE_OUTPUT="-f flv $RTMP_URL" | |
# general text params | |
FONT='fontfile='$FONT_FILE': fontsize='$FONT_SIZE': fontcolor='$TEXT_COLOR': shadowx='$TEXT_SHADOW_W' : shadowy='$TEXT_SHADOW_H | |
TEXT_BG='box=1: boxcolor='$TEXT_BG_COLOR # unused | |
TEXT_BORDER='borderw='$TEXT_BORDER_D': bordercolor='$TEXT_BORDER_COLOR | |
[ ! -z "$TEXT_FILE" ] && [ -f $TEXT_FILE ] && STREAM_TEXT=`cat $TEXT_FILE` | |
BLINK_TEXT_DURATION=$((${#STREAM_TEXT}/$TEXT_MARQUEE_SPEED)) | |
BLINK_TEXT=': enable=lt(mod(t\,'$BLINK_TEXT_PERIOD')\,'$BLINK_TEXT_DURATION')' # unused | |
STATIC_TEXT='text='$STREAM_TEXT # unused | |
TEXT_X_CENTER_POS='x=(main_w-text_w)/2' | |
TEXT_Y_TOP_POS='y=0' # unused | |
TEXT_Y_BOTTOM_POS='y=main_h-(line_h*1.5)' | |
TEXT_X_MARQUEE_POS='x=main_w-((('$SCREEN_W'+text_w)/'$BLINK_TEXT_DURATION')*mod(t\,'$BLINK_TEXT_DURATION'))'$BLINK_TEXT | |
FILE_TEXT="textfile=$TEXT_FILE: reload=1" | |
# general logging params | |
LOGGING_NONE="-loglevel quiet" | |
LOGGING_ERRORS="-loglevel error" | |
LOGGING_VERBOSE="" | |
# user a/v configuration | |
OVERLAY_INPUT=$WEBCAM_INPUT_SM # one of WEBCAM_INPUT_SM , STATIC_INPUT_SM | |
OVERLAY_POS=$OVERLAY_BR_POS # one of OVERLAY_TL_POS , OVERLAY_BR_POS | |
AUDIO_INPUT=$JACK_INPUT # one of ALSA_INPUT , PULSE_INPUT , JACK_INPUT | |
VIDEO_OUTPUT=$X264_OUTPUT # only X264_OUTPUT is defined (others are possible) | |
AUDIO_OUTPUT=$AAC_OUTPUT # only AAC_OUTPUT is defined (others are possible) | |
STREAM_OUTPUT=$LIVE_OUTPUT # one of FILE_OUTPUT , LIVE_OUTPUT | |
# general a/v configuration | |
[ ! -z "$TEXT_FILE" ] && [ ! -f $TEXT_FILE ] && echo "$STREAM_TEXT" > $TEXT_FILE | |
(($CAPTURE_AUDIO)) || (AUDIO_INPUT="" ; AUDIO_OUTPUT="") | |
# user text configuration | |
TEXT=": $FILE_TEXT" | |
# TEXT_POS=": $TEXT_X_CENTER_POS: $TEXT_Y_BOTTOM_POS" | |
TEXT_POS=": $TEXT_X_MARQUEE_POS: $TEXT_Y_BOTTOM_POS" | |
TEXT_DECORATIONS=": $TEXT_BORDER" | |
DRAWTEXT_PARAMS="$FONT $TEXT $TEXT_POS $TEXT_DECORATIONS" | |
# user logging configuration | |
LOGGING=$LOGGING_NONE # console output (one of LOGGING_NONE , LOGGING_ERRORS , LOGGING_VERBOSE) | |
screenOnly() | |
{ | |
printf "\nlaunching screenOnly configuration\n\n" | |
ffmpeg $SCREENCAP_INPUT \ | |
$AUDIO_INPUT \ | |
$VIDEO_OUTPUT \ | |
$AUDIO_OUTPUT \ | |
$THREADS \ | |
$LOGGING \ | |
$STREAM_OUTPUT | |
QUIT=$? | |
echo "QUIT=$QUIT" | |
} | |
screenPlusText() | |
{ | |
printf "\nlaunching screenPlusText configuration\n\n" | |
ffmpeg $SCREENCAP_INPUT \ | |
-filter_complex drawtext="$DRAWTEXT_PARAMS" \ | |
$AUDIO_INPUT \ | |
$VIDEO_OUTPUT \ | |
$AUDIO_OUTPUT \ | |
$THREADS \ | |
$LOGGING \ | |
$STREAM_OUTPUT | |
QUIT=$? | |
} | |
screenPlusCam() | |
{ | |
printf "\nlaunching screenPlusCam configuration\n\n" | |
avconv $SCREENCAP_INPUT \ | |
$OVERLAY_INPUT \ | |
-filter_complex overlay="$OVERLAY_POS" \ | |
$AUDIO_INPUT \ | |
$VIDEO_OUTPUT \ | |
$AUDIO_OUTPUT \ | |
$THREADS \ | |
$LOGGING \ | |
$STREAM_OUTPUT | |
QUIT=$? | |
} | |
camPlusText() | |
{ | |
printf "\nlaunching camPlusText configuration\n\n" | |
ffmpeg $WEBCAM_INPUT_LG \ | |
-filter_complex drawtext="$DRAWTEXT_PARAMS" \ | |
$AUDIO_INPUT \ | |
$VIDEO_OUTPUT \ | |
$AUDIO_OUTPUT \ | |
$THREADS \ | |
$LOGGING \ | |
$STREAM_OUTPUT | |
QUIT=$? | |
} | |
screenPlusCamPlusText() | |
{ | |
printf "\nlaunching screenPlusCamPlusText configuration\n\n" | |
ffmpeg $SCREENCAP_INPUT \ | |
$OVERLAY_INPUT \ | |
-filter_complex "overlay='$OVERLAY_POS', drawtext='$DRAWTEXT_PARAMS'" \ | |
$AUDIO_INPUT \ | |
$VIDEO_OUTPUT \ | |
$AUDIO_OUTPUT \ | |
$THREADS \ | |
$LOGGING \ | |
$STREAM_OUTPUT | |
QUIT=$? | |
} | |
interstitial() | |
{ | |
printf "\nlaunching interstitial configuration\n\n" | |
ffmpeg $STATIC_INPUT_LG \ | |
$AUDIO_INPUT \ | |
$VIDEO_OUTPUT \ | |
$AUDIO_OUTPUT \ | |
$THREADS \ | |
$LOGGING \ | |
$STREAM_OUTPUT | |
QUIT=$? | |
} | |
launch() | |
{ | |
printf "\nstream started at `date`\n\n$QUIT_MSG" ; | |
$SELECTED_CONFIGURATION | |
printf "\nstream ended at `date`\n\n" ; | |
} | |
# swicth on configuration | |
SELECTED_CONFIGURATION="screenPlusCamPlusText" | |
[ "$1" == "screen" ] && SELECTED_CONFIGURATION="screenOnly" | |
[ "$1" == "screen+text" ] && SELECTED_CONFIGURATION="screenPlusText" | |
[ "$1" == "screen+cam" ] && SELECTED_CONFIGURATION="screenPlusCam" | |
[ "$1" == "cam+text" ] && SELECTED_CONFIGURATION="camPlusText" | |
[ "$1" == "all" ] && SELECTED_CONFIGURATION="screenPlusCamPlusText" | |
[ "$1" == "break" ] && SELECTED_CONFIGURATION="interstitial" | |
# sanity checks | |
if [ -z "$STREAM_KEY" ] ; then echo "STREAM_KEY undefined" ; exit ; fi ; | |
if [ -z "$STREAM_IMG" ] && [ "$SELECTED_CONFIGURATION" == "interstitial" ] | |
then echo "STREAM_IMG undefined" ; exit ; fi ; | |
# launch configuration once | |
QUIT_MSG="press <q> to exit" | |
if [ "$2" == "once" ] ; then launch ; exit ; fi ; | |
# launch configuration indefinitely | |
QUIT_MSG="press <q> to restart the stream - press <CTRL-c> to exit" | |
# while ! (($QUIT)) ; do sleep 1 ; launch ; $EXIT_CMD ; done ; | |
# TODO: ffmpeg exits with 0 on server kick - need some way to trap this | |
while ((1)) ; do sleep 1 ; launch ; $EXIT_CMD ; done ; |
#!/bin/bash | |
# NOTE: this script is not yet working properly | |
# screencap works well as do videotestsrc and audiotestsrc | |
# but using alsa , pulse , or jack audio cause the audio to be intermittent | |
# e.g. recording "test 1 2 3 4 5 6 7 8 9" results like "test 3 6 8 9" | |
# configuration | |
WIDTH=1280 | |
HEIGHT=800 | |
FPS=12 | |
VIDEO_BITRATE=1200 | |
SAMPLERATE=44100 | |
AUDIO_BITRATE=128 | |
N_CHANNELS=2 | |
# constants | |
VIDEO_TEST_SRC='videotestsrc pattern=0 is-live=true' | |
AUDIO_TEST_SRC='audiotestsrc is-live=true' | |
# SCREENCAP_SRC="ximagesrc endx=$(($WIDTH-1)) endy=$(($HEIGHT-1)) use-damage=false show-pointer=true" | |
# TIME_SRC='timeoverlay halignment=left valignment=top text="t:" shaded-background=true' | |
PULSE_SRC='pulsesrc' | |
H264_ENCODER="x264enc bitrate=$VIDEO_BITRATE bframes=0" | |
LAME_ENCODER="lamemp3enc bitrate=$AUDIO_BITRATE" | |
# VIDEO_TEST_CAPS="video/x-raw, format=(string)BGRA, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive, framerate=$FPS/1, width=$WIDTH, height=$HEIGHT" | |
# AUDIO_TEST_CAPS="audio/x-raw, format=(string)S16LE, endianness=(int)1234, signed=(boolean)true, width=(int)16, depth=(int)16, rate=(int)$SAMPLERATE, channels=(int)$N_CHANNELS" | |
# PULSE_CAPS=" audio/x-raw, format=(string)S16LE, rate=(int)$SAMPLERATE, channels=(int)$N_CHANNELS, layout=(string)interleaved" | |
VIDEO_TEST_CAPS="video/x-raw, format=(string)BGRA, framerate=(fraction)$FPS/1, width=(int)$WIDTH, height=(int)$HEIGHT" | |
AUDIO_TEST_CAPS="audio/x-raw, format=(string)S16LE, rate=(int)$SAMPLERATE, channels=(int)$N_CHANNELS" | |
SCREENCAP_CAPS=" video/x-raw, framerate=(fraction)$FPS/1, width=(int)$WIDTH, height=(int)$HEIGHT, pixel-aspect-ratio=(fraction)1/1" | |
PULSE_CAPS=" audio/x-raw, format=(string)S16LE, rate=(int)$SAMPLERATE, channels=(int)$N_CHANNELS" | |
H264_ENC_CAPS=' video/x-h264, level=(string)4.1, profile=main' | |
FLV_MUXER='flvmux streamable=true name=mux' | |
OUTPUT_FILE=./deleteme.flv | |
FILE_OUTPUT="filesink location=$OUTPUT_FILE" | |
DEBUG_LOGGING='--gst-debug-level=2' | |
VERBOSE_LOGGING='--verbose' | |
# input selections | |
VIDEO_SRC=$VIDEO_TEST_SRC | |
VIDEO_CAPS=$VIDEO_TEST_CAPS | |
# OVERLAY_SRC=$TIME_SRC # NOTE: put OVERLAY_SRC between VIDEO_SRC and VIDEO_CAPS | |
VIDEO_ENC_CAPS=$H264_ENC_CAPS | |
VIDEO_ENCODER=$H264_ENCODER | |
# AUDIO_SRC=$AUDIO_TEST_SRC | |
AUDIO_SRC=$PULSE_SRC | |
# AUDIO_CAPS=$AUDIO_TEST_CAPS | |
AUDIO_CAPS=$PULSE_CAPS | |
AUDIO_ENCODER=$LAME_ENCODER | |
MUXER=$FLV_MUXER | |
OUTPUT_STREAM=$FILE_OUTPUT | |
LOGGING="$DEBUG_LOGGING $VERBOSE_LOGGING" | |
gst-launch-1.0 $LOGGING $VIDEO_SRC ! \ | |
$VIDEO_CAPS ! \ | |
queue ! \ | |
videoconvert ! \ | |
$VIDEO_ENCODER ! \ | |
h264parse ! \ | |
$VIDEO_ENC_CAPS ! \ | |
queue ! \ | |
mux. \ | |
$AUDIO_SRC ! \ | |
$AUDIO_CAPS ! \ | |
queue ! \ | |
queue ! \ | |
$AUDIO_ENCODER ! \ | |
mpegaudioparse ! \ | |
queue ! \ | |
queue ! \ | |
queue ! \ | |
mux. \ | |
$MUXER ! \ | |
queue ! \ | |
$OUTPUT_STREAM |