From 49f20074f0ff09229b21370b4a52741a10cd583e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delabre?= Date: Thu, 2 Apr 2020 19:32:59 +0200 Subject: [PATCH 1/5] Allow choosing the video output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of simply playing back the frames through `ffplay`, I thought it might be interesting to be able to record the sequence to a video file or to use it as part of a stream. I have in mind the use case of making educational videos/live streams where the tablet can be used as a kind of remote blackboard by teachers, which is especially relevant currently. But there are certainly other use cases! Changes ======= This commit adds two new options to that effect: * `-o --output`: Path of the output as understood by `ffmpeg` (usually a file name). If this is `-` (as it is by default), the existing behavior of playing the stream through `ffplay` is restored. * `-f --format`: When recording to an output, this option can be used to force the encoding format. If this is `-` (again, the default), `ffmpeg`’s auto format detection is used (based on the file extension). Because of the possible confusion between the newly added `--output` option and the existing `--destination` option for specifying the source address, I suggest renaming the `--destination` option to `--source` (this is implemented in this commit). Examples ======== Record to a file ---------------- ```sh ./reStream.sh -o remarkable.mp4 ``` Caveat: The recorded file plays back too fast. I am not sure how to fix this. Create an UDP MPEG-TS stream ---------------------------- ```sh ./reStream.sh -o "udp://127.0.0.1:1234" -f "mpegts" ``` This sends frames over UDP to the specified port using the MPEG-TS format (see ). This stream can then be connected, for example, to OBS for live streaming (see in French). --- README.md | 4 +++- reStream.sh | 43 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d28ab54..a9aabd2 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ reMarkable screen sharing over SSH. ### Options - `-p --portrait`: shows the reMarkable screen in portrait mode (the default is landscape mode, 90 degrees rotated tot the right) -- `-d --destination`: the ssh destination of the reMarkable (default: `root@10.11.99.1`) +- `-s --source`: the ssh destination of the reMarkable (default: `root@10.11.99.1`) +- `-o --output`: path of the output where the video should be recorded, as understood by `ffmpeg`; if this is `-`, the video is displayed in a new window and not recorded anywhere (default: `-`) +- `-f --format`: when recording to an output, this option is used to force the encoding format; if this is `-`, `ffmpeg`’s auto format detection based on the file extension is used (default: `-`). If you have problems, don't hesitate to [open an issue](https://github.com/rien/reStream/issues/new) or [send me an email](mailto:rien.maertens@posteo.be). diff --git a/reStream.sh b/reStream.sh index efafff6..6e5e138 100755 --- a/reStream.sh +++ b/reStream.sh @@ -1,8 +1,10 @@ #!/bin/sh # default values for arguments -ssh_host="root@10.11.99.1" # remarkable connected trough USB +ssh_host="root@10.11.99.1" # remarkable connected through USB landscape=true # rotate 90 degrees to the right +output_path=- # display output through ffplay +format=- # automatic output format # loop through arguments and process them while [ $# -gt 0 ]; do @@ -11,13 +13,23 @@ while [ $# -gt 0 ]; do landscape=false shift ;; - -d|--destination) + -s|--source) ssh_host="$2" shift shift ;; + -o|--output) + output_path="$2" + shift + shift + ;; + -f|--format) + format="$2" + shift + shift + ;; *) - echo "Usage: $0 [-p] [-d ]" + echo "Usage: $0 [-p] [-s ] [-o ] [-f ]" exit 1 esac done @@ -66,11 +78,13 @@ fi -# calculte how much bytes the window is +output_args=() + +# calculate how much bytes the window is window_bytes="$(($width*$height*$bytes_per_pixel))" # rotate 90 degrees if landscape=true -landscape_param="$($landscape && echo '-vf transpose=1')" +$landscape && output_args+=('-vf' 'transpose=1') # read the first $window_bytes of the framebuffer head_fb0="dd if=/dev/fb0 count=1 bs=$window_bytes 2>/dev/null" @@ -78,14 +92,27 @@ head_fb0="dd if=/dev/fb0 count=1 bs=$window_bytes 2>/dev/null" # loop that keeps on reading and compressing, to be executed remotely read_loop="while $head_fb0; do $loop_wait; done | $compress" +if [ "$output_path" = - ]; then + output_command=ffplay +else + output_command=ffmpeg + + if [ "$format" != - ]; then + output_args+=('-f' "$format") + fi + + output_args+=("$output_path") +fi + set -e # stop if an error occurs $ssh_cmd "$read_loop" \ | $decompress \ - | ffplay -vcodec rawvideo \ + | "$output_command" \ + -vcodec rawvideo \ -loglevel "$loglevel" \ -f rawvideo \ -pixel_format gray16le \ -video_size "$width,$height" \ - $landscape_param \ - -i - + -i - \ + "${output_args[@]}" From 43449034382b1cf64c25cdf27f489baad3f7a0a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delabre?= Date: Fri, 3 Apr 2020 11:25:20 +0200 Subject: [PATCH 2/5] Fix recorded videos playing back too fast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the `setpts` video filter to set each frame’s presentation timestamp to the time it is received by the encoder. See . --- reStream.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/reStream.sh b/reStream.sh index 6e5e138..8cdbf2a 100755 --- a/reStream.sh +++ b/reStream.sh @@ -79,12 +79,13 @@ fi output_args=() +video_filters=() # calculate how much bytes the window is window_bytes="$(($width*$height*$bytes_per_pixel))" # rotate 90 degrees if landscape=true -$landscape && output_args+=('-vf' 'transpose=1') +$landscape && video_filters+=('transpose=1') # read the first $window_bytes of the framebuffer head_fb0="dd if=/dev/fb0 count=1 bs=$window_bytes 2>/dev/null" @@ -104,6 +105,12 @@ else output_args+=("$output_path") fi +# set frame presentation time to the time it was received +video_filters+=('setpts=(RTCTIME - RTCSTART) / (TB * 1000000)') + +joined_filters=$(IFS=,; echo "${video_filters[*]}") +output_args+=('-vf' "$joined_filters") + set -e # stop if an error occurs $ssh_cmd "$read_loop" \ From d0323daef57b3a0313169d360b652aff05058f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delabre?= <1370040+matteodelabre@users.noreply.github.com> Date: Tue, 7 Apr 2020 10:48:51 +0200 Subject: [PATCH 3/5] Replace arrays with variable substitutions for POSIX compliance Co-Authored-By: Rien --- reStream.sh | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/reStream.sh b/reStream.sh index 8cdbf2a..9bd30c1 100755 --- a/reStream.sh +++ b/reStream.sh @@ -78,14 +78,14 @@ fi -output_args=() -video_filters=() +output_args="" +video_filters="" # calculate how much bytes the window is window_bytes="$(($width*$height*$bytes_per_pixel))" # rotate 90 degrees if landscape=true -$landscape && video_filters+=('transpose=1') +$landscape && video_filters="$video_filters,transpose=1" # read the first $window_bytes of the framebuffer head_fb0="dd if=/dev/fb0 count=1 bs=$window_bytes 2>/dev/null" @@ -99,17 +99,16 @@ else output_command=ffmpeg if [ "$format" != - ]; then - output_args+=('-f' "$format") + output_args="$output_args -f '$format' " fi - output_args+=("$output_path") + output_args="$output_args '$output_path'" fi # set frame presentation time to the time it was received -video_filters+=('setpts=(RTCTIME - RTCSTART) / (TB * 1000000)') +video_filters="$video_filters,setpts=(RTCTIME - RTCSTART) / (TB * 1000000)" -joined_filters=$(IFS=,; echo "${video_filters[*]}") -output_args+=('-vf' "$joined_filters") +output_args="$output_args -vf '${video_filters#,}'" set -e # stop if an error occurs From 3609b990fff6a4118fa42ce57e7a93d6f58b566d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delabre?= Date: Tue, 7 Apr 2020 11:17:45 +0200 Subject: [PATCH 4/5] Store extra ffmpeg arguments in $@ --- reStream.sh | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/reStream.sh b/reStream.sh index 402a3fa..f178ecf 100755 --- a/reStream.sh +++ b/reStream.sh @@ -71,7 +71,7 @@ fi if [ -z "$compress" ]; then echo "Your remarkable does not have lz4." fallback_to_gzip -elif ! which lz4; then +elif ! lz4 -V; then echo "Your host does not have lz4." fallback_to_gzip else @@ -80,39 +80,41 @@ fi - -output_args="" +# list of ffmpeg filters to apply video_filters="" +# store extra ffmpeg arguments in $@ +set -- + # calculate how much bytes the window is window_bytes="$((width * height * bytes_per_pixel))" # rotate 90 degrees if landscape=true $landscape && video_filters="$video_filters,transpose=1" +# set each frame presentation time to the time it is received +video_filters="$video_filters,setpts=(RTCTIME - RTCSTART) / (TB * 1000000)" + # read the first $window_bytes of the framebuffer head_fb0="dd if=/dev/fb0 count=1 bs=$window_bytes 2>/dev/null" # loop that keeps on reading and compressing, to be executed remotely read_loop="while $head_fb0; do $loop_wait; done | $compress" +set -- "$@" -vf "${video_filters#,}" + if [ "$output_path" = - ]; then output_cmd=ffplay else output_cmd=ffmpeg if [ "$format" != - ]; then - output_args="$output_args -f '$format' " + set -- "$@" -f "$format" fi - output_args="$output_args '$output_path'" + set -- "$@" "$output_path" fi -# set frame presentation time to the time it was received -video_filters="$video_filters,setpts=(RTCTIME - RTCSTART) / (TB * 1000000)" - -output_args="$output_args -vf '${video_filters#,}'" - set -e # stop if an error occurs # shellcheck disable=SC2086 @@ -125,4 +127,4 @@ ssh_cmd "$read_loop" \ -pixel_format gray16le \ -video_size "$width,$height" \ -i - \ - "${output_args[@]}" + "$@" From ab6fa11982a8883ec386468500ff29b3635dfc74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delabre?= Date: Tue, 7 Apr 2020 11:21:10 +0200 Subject: [PATCH 5/5] Fix formatting --- reStream.sh | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/reStream.sh b/reStream.sh index f178ecf..79284b5 100755 --- a/reStream.sh +++ b/reStream.sh @@ -13,17 +13,17 @@ while [ $# -gt 0 ]; do landscape=false shift ;; - -s|--source) + -s | --source) ssh_host="$2" shift shift ;; - -o|--output) + -o | --output) output_path="$2" shift shift ;; - -f|--format) + -f | --format) format="$2" shift shift @@ -78,8 +78,6 @@ else decompress="lz4 -d" fi - - # list of ffmpeg filters to apply video_filters="" @@ -121,10 +119,10 @@ set -e # stop if an error occurs ssh_cmd "$read_loop" \ | $decompress \ | "$output_cmd" \ - -vcodec rawvideo \ - -loglevel "$loglevel" \ - -f rawvideo \ - -pixel_format gray16le \ - -video_size "$width,$height" \ - -i - \ - "$@" + -vcodec rawvideo \ + -loglevel "$loglevel" \ + -f rawvideo \ + -pixel_format gray16le \ + -video_size "$width,$height" \ + -i - \ + "$@"