-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support reMarkable 2 #31
Changes from all commits
995486e
d436a8e
7eb0143
c90bfff
cdd88ac
0dc0549
221aa0e
2e280f8
9352090
4c27256
a024876
4203ac7
794db4c
1b213ad
38ff447
c41b877
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[target.armv7-unknown-linux-gnueabihf] | ||
linker = "/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-linux/usr/bin/arm-oe-linux-gnueabi/arm-oe-linux-gnueabi-gcc" | ||
rustflags = [ | ||
"-C", "link-arg=-march=armv7-a", | ||
"-C", "link-arg=-marm", | ||
"-C", "link-arg=-mfpu=neon", | ||
"-C", "link-arg=-mfloat-abi=hard", | ||
"-C", "link-arg=-mcpu=cortex-a9", | ||
"-C", "link-arg=--sysroot=/usr/local/oecore-x86_64/sysroots/cortexa9hf-neon-oe-linux-gnueabi", | ||
] | ||
|
||
[build] | ||
# Set the default --target flag | ||
target = "armv7-unknown-linux-gnueabihf" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/target |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[package] | ||
name = "restream" | ||
version = "0.1.0" | ||
authors = ["Rien Maertens <rien.maertens@posteo.be>"] | ||
edition = "2018" | ||
|
||
[dependencies] | ||
anyhow = "1.0" | ||
lz-fear = "0.1" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ format=- # automatic output format | |
webcam=false # not to a webcam | ||
measure_throughput=false # measure how fast data is being transferred | ||
window_title=reStream # stream window title is reStream | ||
video_filters="" # list of ffmpeg filters to apply | ||
|
||
# loop through arguments and process them | ||
while [ $# -gt 0 ]; do | ||
|
@@ -75,14 +76,8 @@ while [ $# -gt 0 ]; do | |
esac | ||
done | ||
|
||
# technical parameters | ||
width=1408 | ||
height=1872 | ||
bytes_per_pixel=2 | ||
loop_wait="true" | ||
loglevel="info" | ||
|
||
ssh_cmd() { | ||
echo "[SSH]" "$@" >&2 | ||
ssh -o ConnectTimeout=1 "$ssh_host" "$@" | ||
} | ||
|
||
|
@@ -92,30 +87,45 @@ if ! ssh_cmd true; then | |
exit 1 | ||
fi | ||
|
||
fallback_to_gzip() { | ||
echo "Falling back to gzip, your experience may not be optimal." | ||
echo "Go to https://github.com/rien/reStream/#sub-second-latency for a better experience." | ||
compress="gzip" | ||
decompress="gzip -d" | ||
sleep 2 | ||
} | ||
rm_version="$(ssh_cmd cat /sys/devices/soc0/machine)" | ||
|
||
case "$rm_version" in | ||
"reMarkable 1.0") | ||
width=1408 | ||
height=1872 | ||
pixel_format="rgb565le" | ||
;; | ||
"reMarkable 2.0") | ||
pixel_format="gray8" | ||
width=1872 | ||
height=1404 | ||
video_filters="$video_filters,transpose=2" | ||
;; | ||
*) | ||
echo "Unsupported reMarkable version: $rm_version." | ||
echo "Please visit https://github.com/rien/reStream/ for updates." | ||
exit 1 | ||
;; | ||
esac | ||
|
||
# check if lz4 is present on remarkable | ||
if ssh_cmd "[ -f /opt/bin/lz4 ]"; then | ||
compress="/opt/bin/lz4" | ||
elif ssh_cmd "[ -f ~/lz4 ]"; then | ||
compress="\$HOME/lz4" | ||
fi | ||
# technical parameters | ||
loglevel="info" | ||
decompress="lz4 -d" | ||
|
||
# gracefully degrade to gzip if is not present on remarkable or host | ||
if [ -z "$compress" ]; then | ||
echo "Your remarkable does not have lz4." | ||
fallback_to_gzip | ||
elif ! lz4 -V >/dev/null; then | ||
# check if lz4 is present on the host | ||
if ! lz4 -V >/dev/null; then | ||
echo "Your host does not have lz4." | ||
fallback_to_gzip | ||
else | ||
decompress="lz4 -d" | ||
echo "Please install it using the instruction in the README:" | ||
echo "https://github.com/rien/reStream/#installation" | ||
exit 1 | ||
fi | ||
|
||
# check if restream binay is present on remarkable | ||
if ssh_cmd "[ ! -f ~/restream ]"; then | ||
echo "The restream binary is not installed on your reMarkable." | ||
echo "Please install it using the instruction in the README:" | ||
echo "https://github.com/rien/reStream/#installation" | ||
exit 1 | ||
fi | ||
|
||
# use pv to measure throughput if desired, else we just pipe through cat | ||
|
@@ -131,21 +141,15 @@ else | |
host_passthrough="cat" | ||
fi | ||
|
||
# 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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think transpose might=2 on the RM2 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you confirm this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @harrylepotter indeed, I'm on
|
||
|
||
# Scale and add padding if we are targeting a webcam because a lot of services | ||
# expect a size of exactly 1280x720 (tested in Firefox, MS Teams, and Skype for | ||
# for business). Send a PR is you can get a heigher resolution working. | ||
# for business). Send a PR if you can get a higher resolution working. | ||
if $webcam; then | ||
video_filters="$video_filters,format=pix_fmts=yuv420p" | ||
video_filters="$video_filters,scale=-1:720" | ||
|
@@ -155,12 +159,6 @@ fi | |
# 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 | ||
|
@@ -180,14 +178,14 @@ fi | |
set -e # stop if an error occurs | ||
|
||
# shellcheck disable=SC2086 | ||
ssh_cmd "$read_loop" \ | ||
ssh_cmd "./restream" \ | ||
| $decompress \ | ||
| $host_passthrough \ | ||
| "$output_cmd" \ | ||
-vcodec rawvideo \ | ||
-loglevel "$loglevel" \ | ||
-f rawvideo \ | ||
-pixel_format rgb565le \ | ||
-pixel_format "$pixel_format" \ | ||
-video_size "$width,$height" \ | ||
$window_title_option \ | ||
-i - \ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
#[macro_use] | ||
extern crate anyhow; | ||
extern crate lz_fear; | ||
|
||
use anyhow::{Context, Result}; | ||
use lz_fear::CompressionSettings; | ||
|
||
use std::default::Default; | ||
use std::fs::File; | ||
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom}; | ||
use std::process::Command; | ||
|
||
fn main() -> Result<()> { | ||
let version = remarkable_version()?; | ||
let streamer = if version == "reMarkable 1.0\n" { | ||
let width = 1408; | ||
let height = 1872; | ||
let bytes_per_pixel = 2; | ||
ReStreamer::init("/dev/fb0", 0, width, height, bytes_per_pixel)? | ||
} else if version == "reMarkable 2.0\n" { | ||
let width = 1404; | ||
let height = 1872; | ||
let bytes_per_pixel = 1; | ||
|
||
let pid = xochitl_pid()?; | ||
let offset = rm2_fb_offset(pid)?; | ||
let mem = format!("/proc/{}/mem", pid); | ||
ReStreamer::init(&mem, offset, width, height, bytes_per_pixel)? | ||
} else { | ||
Err(anyhow!( | ||
"Unknown reMarkable version: {}\nPlease open a feature request to support your device.", | ||
version | ||
))? | ||
}; | ||
|
||
let lz4: CompressionSettings = Default::default(); | ||
lz4.compress(streamer, std::io::stdout().lock()) | ||
.context("Error while compressing framebuffer stream") | ||
} | ||
|
||
fn remarkable_version() -> Result<String> { | ||
let content = std::fs::read("/sys/devices/soc0/machine") | ||
.context("Failed to read /sys/devices/soc0/machine")?; | ||
Ok(String::from_utf8(content)?) | ||
} | ||
|
||
fn xochitl_pid() -> Result<usize> { | ||
let output = Command::new("/bin/pidof") | ||
.args(&["xochitl"]) | ||
.output() | ||
.context("Failed to run `/bin/pidof xochitl`")?; | ||
if output.status.success() { | ||
let pid = &output.stdout; | ||
let pid_str = std::str::from_utf8(pid)?.trim(); | ||
pid_str | ||
.parse() | ||
.with_context(|| format!("Failed to parse xochitl's pid: {}", pid_str)) | ||
} else { | ||
Err(anyhow!( | ||
"Could not find pid of xochitl, is xochitl running?" | ||
)) | ||
} | ||
} | ||
|
||
fn rm2_fb_offset(pid: usize) -> Result<usize> { | ||
let file = File::open(format!("/proc/{}/maps", &pid))?; | ||
let line = BufReader::new(file) | ||
.lines() | ||
.skip_while(|line| matches!(line, Ok(l) if !l.ends_with("/dev/fb0"))) | ||
.skip(1) | ||
.next() | ||
.with_context(|| format!("No line containing /dev/fb0 in /proc/{}/maps file", pid))? | ||
.with_context(|| format!("Error reading file /proc/{}/maps", pid))?; | ||
|
||
let addr = line | ||
.split("-") | ||
.next() | ||
.with_context(|| format!("Error parsing line in /proc/{}/maps", pid))?; | ||
|
||
let address = usize::from_str_radix(addr, 16).context("Error parsing framebuffer address")?; | ||
Ok(address + 8) | ||
} | ||
|
||
pub struct ReStreamer { | ||
file: File, | ||
start: u64, | ||
cursor: usize, | ||
size: usize, | ||
} | ||
|
||
impl ReStreamer { | ||
pub fn init( | ||
path: &str, | ||
offset: usize, | ||
width: usize, | ||
height: usize, | ||
bytes_per_pixel: usize, | ||
) -> Result<ReStreamer> { | ||
let start = offset as u64; | ||
let size = width * height * bytes_per_pixel; | ||
let cursor = 0; | ||
let file = File::open(path)?; | ||
let mut streamer = ReStreamer { | ||
file, | ||
start: start, | ||
cursor, | ||
size, | ||
}; | ||
streamer.next_frame()?; | ||
Ok(streamer) | ||
} | ||
|
||
pub fn next_frame(&mut self) -> std::io::Result<()> { | ||
self.file.seek(SeekFrom::Start(self.start))?; | ||
self.cursor = 0; | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl Read for ReStreamer { | ||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { | ||
let requested = buf.len(); | ||
let bytes_read = if self.cursor + requested < self.size { | ||
self.file.read(buf)? | ||
} else { | ||
let rest = self.size - self.cursor; | ||
self.file.read(&mut buf[0..rest])? | ||
}; | ||
self.cursor += bytes_read; | ||
if self.cursor == self.size { | ||
self.next_frame()?; | ||
} | ||
Ok(bytes_read) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have experimented with sleeps, but it ended up not being beneficial I think. Anyway, we should speed up reading the framebuffer before changing
loop_wait
.